diff --git a/CMakeLists.txt b/CMakeLists.txt index bfc75003b9a4..789a3673dfea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(GCC_MINIMUM_VERSION "5.1") set(CLANG_MINIMUM_VERSION "3.5") -# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing +# You should not need to edit these manually, use util/bump_version.sh set(VERSION_MAJOR 5) set(VERSION_MINOR 7) set(VERSION_PATCH 0) @@ -127,7 +127,7 @@ if(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt) endif() message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}") - set(TARGET_VER_S 1.9.0mt7) + set(TARGET_VER_S 1.9.0mt8) string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S}) if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER}) message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build") @@ -136,6 +136,17 @@ if(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt) endif() endif() +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}") + message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. " + "Version ${GCC_MINIMUM_VERSION} or higher is required.") + endif() +elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang") + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_MINIMUM_VERSION}") + message(FATAL_ERROR "Insufficient clang version, found ${CMAKE_CXX_COMPILER_VERSION}. " + "Version ${CLANG_MINIMUM_VERSION} or higher is required.") + endif() +endif() # Installation @@ -280,23 +291,9 @@ find_package(Lua REQUIRED) find_package(Zmq REQUIRED) find_package(Zmqpp REQUIRED) if(NOT USE_LUAJIT) - set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop) - set(LUA_BIT_LIBRARY bitop) add_subdirectory(lib/bitop) endif() -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}") - message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. " - "Version ${GCC_MINIMUM_VERSION} or higher is required.") - endif() -elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang") - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_MINIMUM_VERSION}") - message(FATAL_ERROR "Insufficient clang version, found ${CMAKE_CXX_COMPILER_VERSION}. " - "Version ${CLANG_MINIMUM_VERSION} or higher is required.") - endif() -endif() - if(BUILD_BENCHMARKS) add_subdirectory(lib/catch2) endif() @@ -305,7 +302,6 @@ endif() # Be sure to add all relevant definitions above this add_subdirectory(src) - # CPack set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A free open-source voxel game engine with easy modding and game creation.") diff --git a/LICENSE.txt b/LICENSE.txt index 1f2c6c38de77..33979893944b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -14,6 +14,9 @@ https://www.apache.org/licenses/LICENSE-2.0.html Textures by Zughy are under CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/ +Media files by DS are under CC BY-SA 4.0 +https://creativecommons.org/licenses/by-sa/4.0/ + textures/base/pack/server_public.png is under CC-BY 4.0, taken from Twitter's Twemoji set https://creativecommons.org/licenses/by/4.0/ @@ -55,7 +58,6 @@ srifqi: Zughy: textures/base/pack/cdb_add.png - textures/base/pack/cdb_clear.png textures/base/pack/cdb_downloading.png textures/base/pack/cdb_queued.png textures/base/pack/cdb_update.png @@ -63,14 +65,15 @@ Zughy: appgurueu: textures/base/pack/server_incompatible.png - + erlehmann, Warr1024, rollerozxa: textures/base/pack/no_screenshot.png kilbith: textures/base/pack/server_favorite.png -SmallJoker +SmallJoker: + textures/base/pack/cdb_clear.png textures/base/pack/server_favorite_delete.png (based on server_favorite.png) License of Minetest source code diff --git a/README.md b/README.md index 5830975b3ebc..07361387758a 100644 --- a/README.md +++ b/README.md @@ -112,15 +112,15 @@ Where each location is on each platform: * Windows installed: * `bin` = `C:\Program Files\Minetest\bin (Depends on the install location)` * `share` = `C:\Program Files\Minetest (Depends on the install location)` - * `user` = `%APPDATA%\Minetest` + * `user` = `%APPDATA%\Minetest` or `%MINETEST_USER_PATH%` * Linux installed: * `bin` = `/usr/bin` * `share` = `/usr/share/minetest` - * `user` = `~/.minetest` + * `user` = `~/.minetest` or `$MINETEST_USER_PATH` * macOS: * `bin` = `Contents/MacOS` * `share` = `Contents/Resources` - * `user` = `Contents/User OR ~/Library/Application Support/minetest` + * `user` = `Contents/User` or `~/Library/Application Support/minetest` or `$MINETEST_USER_PATH` Worlds can be found as separate folders in: `user/worlds/` @@ -293,9 +293,10 @@ Library specific options: FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs - GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h - GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a + GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains libintl.h + GETTEXT_LIBRARY - Optional/platform-dependent with gettext; path to libintl.so/libintl.dll.a GETTEXT_MSGFMT - Only when building with gettext; path to msgfmt/msgfmt.exe + ICONV_LIBRARY - Optional/platform-dependent; path to libiconv.so/libiconv.dylib IRRLICHT_DLL - Only on Windows; path to IrrlichtMt.dll IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h (usable for server build only) LEVELDB_INCLUDE_DIR - Only when building with LevelDB; directory that contains db.h diff --git a/builtin/async/game.lua b/builtin/async/game.lua index 6512f07068fd..0b7a7ef0e4a3 100644 --- a/builtin/async/game.lua +++ b/builtin/async/game.lua @@ -13,9 +13,12 @@ end -- Import a bunch of individual files from builtin/game/ local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM +local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM + +local builtin_shared = {} dofile(gamepath .. "constants.lua") -dofile(gamepath .. "item_s.lua") +assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared) dofile(gamepath .. "misc_s.lua") dofile(gamepath .. "features.lua") dofile(gamepath .. "voxelarea.lua") @@ -57,3 +60,5 @@ setmetatable(core.registered_items, alias_metatable) setmetatable(core.registered_nodes, alias_metatable) setmetatable(core.registered_craftitems, alias_metatable) setmetatable(core.registered_tools, alias_metatable) + +builtin_shared.cache_content_ids() diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 3719a90eef39..68fb169f04e6 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -10,3 +10,4 @@ dofile(commonpath .. "chatcommands.lua") dofile(clientpath .. "chatcommands.lua") dofile(clientpath .. "death_formspec.lua") dofile(clientpath .. "misc.lua") +assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions diff --git a/builtin/game/item_s.lua b/builtin/common/item_s.lua similarity index 61% rename from builtin/game/item_s.lua rename to builtin/common/item_s.lua index a51cd0a1ce73..f848ef6d8dab 100644 --- a/builtin/game/item_s.lua +++ b/builtin/common/item_s.lua @@ -4,6 +4,8 @@ -- Server or writable access to IGameDef on the engine side. -- (The '_s' stands for standalone.) +local builtin_shared = ... + -- -- Item definition helpers -- @@ -92,6 +94,26 @@ function core.facedir_to_dir(facedir) return facedir_to_dir[facedir_to_dir_map[facedir % 32]] end +function core.dir_to_fourdir(dir) + if math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 3 + else + return 1 + end + else + if dir.z < 0 then + return 2 + else + return 0 + end + end +end + +function core.fourdir_to_dir(fourdir) + return facedir_to_dir[facedir_to_dir_map[fourdir % 4]] +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 @@ -137,7 +159,8 @@ end function core.is_colored_paramtype(ptype) return (ptype == "color") or (ptype == "colorfacedir") or - (ptype == "colorwallmounted") or (ptype == "colordegrotate") + (ptype == "color4dir") or (ptype == "colorwallmounted") or + (ptype == "colordegrotate") end function core.strip_param2_color(param2, paramtype2) @@ -146,6 +169,8 @@ function core.strip_param2_color(param2, paramtype2) end if paramtype2 == "colorfacedir" then param2 = math.floor(param2 / 32) * 32 + elseif paramtype2 == "color4dir" then + param2 = math.floor(param2 / 4) * 4 elseif paramtype2 == "colorwallmounted" then param2 = math.floor(param2 / 8) * 8 elseif paramtype2 == "colordegrotate" then @@ -154,3 +179,61 @@ function core.strip_param2_color(param2, paramtype2) -- paramtype2 == "color" requires no modification. return param2 end + +-- Content ID caching + +local old_get_content_id = core.get_content_id +local old_get_name_from_content_id = core.get_name_from_content_id + +local name2content = setmetatable({}, { + __index = function(self, name) + return old_get_content_id(name) + end, +}) + +local content2name = setmetatable({}, { + __index = function(self, id) + return old_get_name_from_content_id(id) + end, +}) + +function core.get_content_id(name) + return name2content[name] +end + +function core.get_name_from_content_id(id) + return content2name[id] +end + +-- Cache content IDs after they have stopped changing. +function builtin_shared.cache_content_ids() + for name in pairs(core.registered_nodes) do + local id = old_get_content_id(name) + name2content[name] = id + content2name[id] = name + end + -- unknown is not in the registered node list. + local unknown_name = old_get_name_from_content_id(core.CONTENT_UNKNOWN) + name2content[unknown_name] = core.CONTENT_UNKNOWN + content2name[core.CONTENT_UNKNOWN] = unknown_name + + for name in pairs(core.registered_aliases) do + if core.registered_nodes[name] then + name2content[name] = old_get_content_id(name) + end + end +end + +if core.set_read_node and core.set_push_node then + local function read_node(node) + return name2content[node.name], node.param1, node.param2 + end + core.set_read_node(read_node) + core.set_read_node = nil + + local function push_node(content, param1, param2) + return {name = content2name[content], param1 = param1, param2 = param2} + end + core.set_push_node(push_node) + core.set_push_node = nil +end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 467f18804961..720df3998c3b 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -519,18 +519,6 @@ end -------------------------------------------------------------------------------- -- mainmenu only functions -------------------------------------------------------------------------------- -if INIT == "mainmenu" then - function core.get_game(index) - local games = core.get_games() - - if index > 0 and index <= #games then - return games[index] - end - - return nil - end -end - if core.gettext then -- for client and mainmenu function fgettext_ne(text, ...) text = core.gettext(text) diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index caf989e69fd3..afa63b362bab 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -61,17 +61,21 @@ end -- Serializes Lua nil, booleans, numbers, strings, tables and even functions -- Tables are referenced by reference, strings are referenced by value. Supports circular tables. local function serialize(value, write) - local reference, refnum = "r1", 1 - -- [object] = reference string + local reference, refnum = "1", 1 + -- [object] = reference local references = {} -- Circular tables that must be filled using `table[key] = value` statements local to_fill = {} for object, count in pairs(count_objects(value)) do local type_ = type(object) -- Object must appear more than once. If it is a string, the reference has to be shorter than the string. - if count >= 2 and (type_ ~= "string" or #reference + 2 < #object) then + if count >= 2 and (type_ ~= "string" or #reference + 5 < #object) then + if refnum == 1 then + write"local _={};" -- initialize reference table + end + write"_[" write(reference) - write("=") + write("]=") if type_ == "table" then write("{}") elseif type_ == "function" then @@ -85,7 +89,7 @@ local function serialize(value, write) to_fill[object] = reference end refnum = refnum + 1 - reference = ("r%X"):format(refnum) + reference = ("%d"):format(refnum) end end -- Used to decide whether we should do "key=..." @@ -105,12 +109,22 @@ local function serialize(value, write) end local type_ = type(value) if type_ == "number" then - return write(string_format("%.17g", value)) + if value ~= value then -- nan + return write"0/0" + elseif value == math_huge then + return write"1/0" + elseif value == -math_huge then + return write"-1/0" + else + return write(string_format("%.17g", value)) + end end -- Reference types: table, function and string local ref = references[value] if ref then - return write(ref) + write"_[" + write(ref) + return write"]" end if type_ == "string" then return write(quote(value)) @@ -156,7 +170,9 @@ local function serialize(value, write) -- Write the statements to fill circular tables for table, ref in pairs(to_fill) do for k, v in pairs(table) do + write("_[") write(ref) + write("]") if use_short_key(k) then write(".") write(k) @@ -185,8 +201,6 @@ end local function dummy_func() end -local nan = (0/0)^1 -- +nan - function core.deserialize(str, safe) -- Backwards compatibility if str == nil then @@ -201,8 +215,8 @@ function core.deserialize(str, safe) local func, err = loadstring(str) if not func then return nil, err end - -- math.huge is serialized to inf, NaNs are serialized to nan by Lua - local env = {inf = math_huge, nan = nan} + -- math.huge was serialized to inf and NaNs to nan by Lua in Minetest 5.6, so we have to support this here + local env = {inf = math_huge, nan = 0/0} if safe then env.loadstring = dummy_func else diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index 7d046d5b7d80..ff0f0298ab51 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -1,5 +1,4 @@ _G.core = {} -_G.vector = {metatable = {}} dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua index 340e226ee98d..d4e501468406 100644 --- a/builtin/common/tests/serialize_spec.lua +++ b/builtin/common/tests/serialize_spec.lua @@ -1,5 +1,4 @@ _G.core = {} -_G.vector = {metatable = {}} _G.setfenv = require 'busted.compatibility'.setfenv diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua index 6a0b81a89128..a738e068fcdb 100644 --- a/builtin/common/tests/vector_spec.lua +++ b/builtin/common/tests/vector_spec.lua @@ -1,4 +1,4 @@ -_G.vector = {metatable = {}} +_G.vector = {} dofile("builtin/common/vector.lua") describe("vector", function() diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index a08472e322c7..cebc9958034f 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -6,8 +6,10 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta -- localize functions local setmetatable = setmetatable --- vector.metatable is set by C++. -local metatable = vector.metatable +vector = {} + +local metatable = {} +vector.metatable = metatable local xyz = {"x", "y", "z"} @@ -366,3 +368,22 @@ function vector.dir_to_rotation(forward, up) end return rot end + +if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then + local function read_vector(v) + return v.x, v.y, v.z + end + core.set_read_vector(read_vector) + core.set_read_vector = nil + + if rawget(_G, "jit") then + -- This is necessary to prevent trace aborts. + local function push_vector(x, y, z) + return (fast_new(x, y, z)) + end + core.set_push_vector(push_vector) + else + core.set_push_vector(fast_new) + end + core.set_push_vector = nil +end diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index d5727f2a78d6..01a7d60b88cb 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -161,7 +161,16 @@ core.register_entity(":__builtin:falling_node", { local fdir = node.param2 % 32 % 24 -- Get rotation from a precalculated lookup table local euler = facedir_to_euler[fdir + 1] - self.object:set_rotation(euler) + if euler then + self.object:set_rotation(euler) + end + elseif (def.paramtype2 == "4dir" or def.paramtype2 == "color4dir") then + local fdir = node.param2 % 4 + -- Get rotation from a precalculated lookup table + local euler = facedir_to_euler[fdir + 1] + if euler then + self.object:set_rotation(euler) + end elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then local rot = node.param2 % 8 diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 896a893b216f..334d62acc9c4 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -25,6 +25,8 @@ core.features = { dynamic_add_media_table = true, get_sky_as_table = true, get_light_data_buffer = true, + mod_storage_on_disk = true, + compress_zstd = true, } function core.has_feature(arg) diff --git a/builtin/game/init.lua b/builtin/game/init.lua index d7606f357fc8..b9d2c0baf785 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -8,7 +8,7 @@ local gamepath = scriptpath .. "game".. DIR_DELIM local builtin_shared = {} dofile(gamepath .. "constants.lua") -dofile(gamepath .. "item_s.lua") +assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared) assert(loadfile(gamepath .. "item.lua"))(builtin_shared) dofile(gamepath .. "register.lua") @@ -37,4 +37,6 @@ dofile(gamepath .. "statbars.lua") dofile(gamepath .. "knockback.lua") dofile(gamepath .. "async.lua") +core.after(0, builtin_shared.cache_content_ids) + profiler = nil diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 00601c68cd6e..2e8fcc343ac0 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -205,7 +205,9 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, 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 + def.paramtype2 == "colorfacedir" or + def.paramtype2 == "4dir" or + def.paramtype2 == "color4dir") and not param2 then local placer_pos = placer and placer:get_pos() if placer_pos then local dir = vector.subtract(above, placer_pos) @@ -225,6 +227,8 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, color_divisor = 8 elseif def.paramtype2 == "colorfacedir" then color_divisor = 32 + elseif def.paramtype2 == "color4dir" then + color_divisor = 4 elseif def.paramtype2 == "colordegrotate" then color_divisor = 32 end @@ -345,8 +349,26 @@ function core.item_drop(itemstack, dropper, pos) -- environment failed end +function core.item_pickup(itemstack, picker, pointed_thing, ...) + itemstack = ItemStack(itemstack) + -- Invoke global on_item_pickup callbacks. + for _, callback in ipairs(core.registered_on_item_pickups) do + local result = callback(itemstack, picker, pointed_thing, ...) + if result then + return ItemStack(result) + end + end + + -- Pickup item. + local inv = picker and picker:get_inventory() + if inv then + return inv:add_item("main", itemstack) + end + return itemstack +end + function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing) - for _, callback in pairs(core.registered_on_item_eats) do + for _, callback in ipairs(core.registered_on_item_eats) do local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing) if result then return result @@ -585,6 +607,7 @@ core.nodedef_default = { -- Interaction callbacks on_place = redef_wrapper(core, 'item_place'), -- core.item_place on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop + on_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup on_use = nil, can_dig = nil, @@ -637,6 +660,7 @@ core.craftitemdef_default = { -- Interaction callbacks on_place = redef_wrapper(core, 'item_place'), -- core.item_place on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop + on_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup on_secondary_use = redef_wrapper(core, 'item_secondary_use'), on_use = nil, } @@ -657,6 +681,7 @@ core.tooldef_default = { 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_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup on_use = nil, } @@ -673,8 +698,9 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items tool_capabilities = nil, -- Interaction callbacks - on_place = redef_wrapper(core, 'item_place'), + on_place = redef_wrapper(core, 'item_place'), -- core.item_place on_secondary_use = redef_wrapper(core, 'item_secondary_use'), + on_pickup = redef_wrapper(core, 'item_pickup'), -- core.item_pickup on_drop = nil, on_use = nil, } diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index 53f98a7c73c1..f321bb1dd003 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -318,16 +318,29 @@ core.register_entity(":__builtin:item", { 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:set_item(left) - return - end + on_punch = function(self, hitter, ...) + if self.itemstring == "" then + self.object:remove() + return + end + + -- Call on_pickup callback in item definition. + local itemstack = ItemStack(self.itemstring) + local callback = itemstack:get_definition().on_pickup + + local ret = callback(itemstack, hitter, {type = "object", ref = self.object}, ...) + if not ret then + -- Don't modify (and don't reset rotation) + return + end + itemstack = ItemStack(ret) + + -- Handle the leftover itemstack + if itemstack:is_empty() then + self.itemstring = "" + self.object:remove() + else + self:set_item(itemstack) end - self.itemstring = "" - self.object:remove() end, }) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 8b6f5b990648..d4c8768981f6 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -607,6 +607,7 @@ 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_item_pickups, core.register_on_item_pickup = make_registration() core.registered_on_punchplayers, core.register_on_punchplayer = make_registration() core.registered_on_priv_grant, core.register_on_priv_grant = make_registration() core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration() diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua index 62f07d928f90..a9195213bdbd 100644 --- a/builtin/game/voxelarea.lua +++ b/builtin/game/voxelarea.lua @@ -8,7 +8,10 @@ VoxelArea = { zstride = 0, } -function VoxelArea:new(o) +local class_metatable = {} +setmetatable(VoxelArea, class_metatable) + +local function new(self, o) o = o or {} setmetatable(o, self) self.__index = self @@ -20,6 +23,12 @@ function VoxelArea:new(o) return o end +function class_metatable:__call(MinEdge, MaxEdge) + return new(self, {MinEdge = MinEdge, MaxEdge = MaxEdge}) +end + +VoxelArea.new = new + function VoxelArea:getExtent() local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge return vector_new( diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index 81e28f2bb3b6..4713605812ea 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -45,6 +45,27 @@ local function configure_selected_world_params(idx) end end +-- retrieved from https://wondernetwork.com/pings with (hopefully) representative cities +-- Amsterdam, Auckland, Brasilia, Denver, Lagos, Singapore +local latency_matrix = { + ["AF"] = { ["AS"]=258, ["EU"]=100, ["NA"]=218, ["OC"]=432, ["SA"]=308 }, + ["AS"] = { ["EU"]=168, ["NA"]=215, ["OC"]=125, ["SA"]=366 }, + ["EU"] = { ["NA"]=120, ["OC"]=298, ["SA"]=221 }, + ["NA"] = { ["OC"]=202, ["SA"]=168 }, + ["OC"] = { ["SA"]=411 }, + ["SA"] = {} +} +function estimate_continent_latency(own, spec) + local there = spec.geo_continent + if not own or not there then + return nil + end + if own == there then + return 0 + end + return latency_matrix[there][own] or latency_matrix[own][there] +end + function render_serverlist_row(spec) local text = "" if spec.name then diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua index e76e10ef7d6f..9a5562a570be 100644 --- a/builtin/mainmenu/dlg_config_world.lua +++ b/builtin/mainmenu/dlg_config_world.lua @@ -57,8 +57,7 @@ local function init_data(data) hide_game = data.hide_gamemods, hide_modpackcontents = data.hide_modpackcontents }) - data.list:add_sort_mechanism("alphabetic", sort_mod_list) - data.list:set_sortmode("alphabetic") + -- Sorting is already done by pgkmgr.get_mods end diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua index 32054fae35d4..9b73b526e8f4 100644 --- a/builtin/mainmenu/dlg_contentstore.lua +++ b/builtin/mainmenu/dlg_contentstore.lua @@ -346,22 +346,21 @@ end local install_dialog = {} function install_dialog.get_formspec() + local selected_game, selected_game_idx = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) + if not selected_game_idx then + selected_game_idx = 1 + selected_game = pkgmgr.games[1] + end + + local game_list = {} + for i, game in ipairs(pkgmgr.games) do + game_list[i] = core.formspec_escape(game.title) + end + local package = install_dialog.package local raw_deps = install_dialog.raw_deps local will_install_deps = install_dialog.will_install_deps - local selected_game_idx = 1 - local selected_gameid = core.settings:get("menu_last_game") - local games = table.copy(pkgmgr.games) - for i=1, #games do - if selected_gameid and games[i].id == selected_gameid then - selected_game_idx = i - end - - games[i] = core.formspec_escape(games[i].title) - end - - local selected_game = pkgmgr.games[selected_game_idx] local deps_to_install = 0 local deps_not_found = 0 @@ -408,7 +407,7 @@ function install_dialog.get_formspec() "container[0.375,0.70]", "label[0,0.25;", fgettext("Base Game:"), "]", - "dropdown[2,0;4.25,0.5;selected_game;", table.concat(games, ","), ";", selected_game_idx, "]", + "dropdown[2,0;4.25,0.5;selected_game;", table.concat(game_list, ","), ";", selected_game_idx, "]", "label[0,0.8;", fgettext("Dependencies:"), "]", diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 806e019a96e2..2f3ef596cda8 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -104,14 +104,12 @@ local function create_world_formspec(dialogdata) local current_mg = dialogdata.mg local mapgens = core.get_mapgen_names() - local gameid = core.settings:get("menu_last_game") - local flags = dialogdata.flags - local game = pkgmgr.find_by_gameid(gameid) + local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) if game == nil then -- should never happen but just pick the first game - game = pkgmgr.get_game(1) + game = pkgmgr.games[1] core.settings:set("menu_last_game", game.id) end @@ -355,7 +353,7 @@ local function create_world_buttonhandler(this, fields) fields["key_enter"] then local worldname = fields["te_world_name"] - local game, gameindex = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) + local game, _ = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) local message if game == nil then @@ -399,7 +397,7 @@ local function create_world_buttonhandler(this, fields) mgvalleys_spflags = table_to_flags(this.data.flags.valleys), mgflat_spflags = table_to_flags(this.data.flags.flat), } - message = core.create_world(worldname, gameindex, settings) + message = core.create_world(worldname, game.id, settings) end if message == nil then diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua index 69562e6a544b..32c051c26d19 100644 --- a/builtin/mainmenu/dlg_settings_advanced.lua +++ b/builtin/mainmenu/dlg_settings_advanced.lua @@ -344,9 +344,7 @@ local function parse_config_file(read_all, parse_mods) if parse_mods then -- Parse games local games_category_initialized = false - local index = 1 - local game = pkgmgr.get_game(index) - while game do + for _, game in ipairs(pkgmgr.games) do local path = game.path .. DIR_DELIM .. FILENAME local file = io.open(path, "r") if file then @@ -370,15 +368,12 @@ local function parse_config_file(read_all, parse_mods) file:close() end - - index = index + 1 - game = pkgmgr.get_game(index) end -- Parse mods local mods_category_initialized = false local mods = {} - get_mods(core.get_modpath(), "mods", mods) + pkgmgr.get_mods(core.get_modpath(), "mods", mods) for _, mod in ipairs(mods) do local path = mod.path .. DIR_DELIM .. FILENAME local file = io.open(path, "r") @@ -409,7 +404,7 @@ local function parse_config_file(read_all, parse_mods) -- Parse client mods local clientmods_category_initialized = false local clientmods = {} - get_mods(core.get_clientmodpath(), "clientmods", clientmods) + pkgmgr.get_mods(core.get_clientmodpath(), "clientmods", clientmods) for _, mod in ipairs(clientmods) do local path = mod.path .. DIR_DELIM .. FILENAME local file = io.open(path, "r") diff --git a/builtin/mainmenu/dlg_version_info.lua b/builtin/mainmenu/dlg_version_info.lua index 568fca3f493c..bb47142a071c 100644 --- a/builtin/mainmenu/dlg_version_info.lua +++ b/builtin/mainmenu/dlg_version_info.lua @@ -36,9 +36,8 @@ local function version_info_formspec(data) "formspec_version[3]", "size[12.8,7]", "style_type[label;textcolor=#0E0]", - "label[0.5,0.8;", core.formspec_escape(title), "]", - "textarea[0.4,1.6;12,3.4;;;", - core.formspec_escape(message), "]", + "label[0.5,0.8;", title, "]", + "textarea[0.4,1.6;12,3.4;;;", message, "]", "container[0.4,5.8]", "button[0.0,0;4.0,0.8;version_check_visit;", fgettext("Visit website"), "]", "button[4.5,0;3.5,0.8;version_check_remind;", fgettext("Later"), "]", diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua index 9fa9aebb0675..51f635962e1e 100644 --- a/builtin/mainmenu/pkgmgr.lua +++ b/builtin/mainmenu/pkgmgr.lua @@ -100,7 +100,15 @@ local function load_texture_packs(txtpath, retval) end end -function get_mods(path, virtual_path, retval, modpack) +--modmanager implementation +pkgmgr = {} + +--- Scans a directory recursively for mods and adds them to `listing` +-- @param path Absolute directory path to scan recursively +-- @param virtual_path Prettified unique path (e.g. "mods", "mods/mt_modpack") +-- @param listing Input. Flat array to insert located mods and modpacks +-- @param modpack Currently processing modpack or nil/"" if none (recursion) +function pkgmgr.get_mods(path, virtual_path, listing, modpack) local mods = core.get_dir_list(path, true) for _, name in ipairs(mods) do @@ -111,7 +119,7 @@ function get_mods(path, virtual_path, retval, modpack) dir_name = name, parent_dir = path, } - retval[#retval + 1] = toadd + listing[#listing + 1] = toadd -- Get config file local mod_conf @@ -156,14 +164,18 @@ function get_mods(path, virtual_path, retval, modpack) elseif toadd.is_modpack then toadd.type = "modpack" toadd.is_modpack = true - get_mods(mod_path, mod_virtual_path, retval, name) + pkgmgr.get_mods(mod_path, mod_virtual_path, listing, name) end end end -end ---modmanager implementation -pkgmgr = {} + if not modpack then + -- Sort all when the recursion is done + table.sort(listing, function(a, b) + return a.virtual_path:lower() < b.virtual_path:lower() + end) + end +end function pkgmgr.get_texture_packs() local txtpath = core.get_texturepath() @@ -177,7 +189,7 @@ function pkgmgr.get_texture_packs() end table.sort(retval, function(a, b) - return a.name > b.name + return a.title:lower() < b.title:lower() end) return retval @@ -593,7 +605,7 @@ function pkgmgr.preparemodlist(data) --read global mods local modpaths = core.get_modpaths() for key, modpath in pairs(modpaths) do - get_mods(modpath, key, global_mods) + pkgmgr.get_mods(modpath, key, global_mods) end for i=1,#global_mods,1 do @@ -700,35 +712,6 @@ function pkgmgr.comparemod(elem1,elem2) return true end --------------------------------------------------------------------------------- -function pkgmgr.mod_exists(basename) - - if pkgmgr.global_mods == nil then - pkgmgr.refresh_globals() - end - - if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then - return true - end - - return false -end - --------------------------------------------------------------------------------- -function pkgmgr.get_global_mod(idx) - - if pkgmgr.global_mods == nil then - return nil - end - - if idx == nil or idx < 1 or - idx > pkgmgr.global_mods:size() then - return nil - end - - return pkgmgr.global_mods:get_list()[idx] -end - -------------------------------------------------------------------------------- function pkgmgr.refresh_globals() local function is_equal(element,uid) --uid match @@ -744,9 +727,9 @@ end -------------------------------------------------------------------------------- function pkgmgr.find_by_gameid(gameid) - for i=1,#pkgmgr.games,1 do - if pkgmgr.games[i].id == gameid then - return pkgmgr.games[i], i + for i, game in ipairs(pkgmgr.games) do + if game.id == gameid then + return game, i end end return nil, nil @@ -757,49 +740,16 @@ function pkgmgr.get_game_mods(gamespec, retval) if gamespec ~= nil and gamespec.gamemods_path ~= nil and gamespec.gamemods_path ~= "" then - get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval) + pkgmgr.get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval) end end --------------------------------------------------------------------------------- -function pkgmgr.get_game_modlist(gamespec) - local retval = "" - local game_mods = {} - pkgmgr.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 pkgmgr.get_game(index) - if index > 0 and index <= #pkgmgr.games then - return pkgmgr.games[index] - end - - return nil -end - -------------------------------------------------------------------------------- function pkgmgr.update_gamelist() pkgmgr.games = core.get_games() -end - --------------------------------------------------------------------------------- -function pkgmgr.gamelist() - local retval = "" - if #pkgmgr.games > 0 then - retval = retval .. core.formspec_escape(pkgmgr.games[1].title) - - for i=2,#pkgmgr.games,1 do - retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].title) - end - end - return retval + table.sort(pkgmgr.games, function(a, b) + return a.title:lower() < b.title:lower() + end) end -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/serverlistmgr.lua b/builtin/mainmenu/serverlistmgr.lua index 964d0c584f2a..ab686ded08b5 100644 --- a/builtin/mainmenu/serverlistmgr.lua +++ b/builtin/mainmenu/serverlistmgr.lua @@ -15,28 +15,101 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -serverlistmgr = {} +serverlistmgr = { + -- continent code we detected for ourselves + my_continent = core.get_once("continent"), + + -- list of locally favorites servers + favorites = nil, + + -- list of servers fetched from public list + servers = nil, +} -------------------------------------------------------------------------------- +-- Efficient data structure for normalizing arbitrary scores attached to objects +-- e.g. {{"a", 3.14}, {"b", 3.14}, {"c", 20}, {"d", 0}} +-- -> {["d"] = 0, ["a"] = 0.5, ["b"] = 0.5, ["c"] = 1} +local Normalizer = {} + +function Normalizer:new() + local t = { + map = {} + } + setmetatable(t, self) + self.__index = self + return t +end + +function Normalizer:push(obj, score) + if not self.map[score] then + self.map[score] = {} + end + local t = self.map[score] + t[#t + 1] = obj +end + +function Normalizer:calc() + local list = {} + for k, _ in pairs(self.map) do + list[#list + 1] = k + end + table.sort(list) + local ret = {} + for i, k in ipairs(list) do + local score = #list == 1 and 1 or ( (i - 1) / (#list - 1) ) + for _, obj in ipairs(self.map[k]) do + ret[obj] = score + end + end + return ret +end + +-------------------------------------------------------------------------------- +-- how much the pre-sorted server list contributes to the final ranking +local WEIGHT_SORT = 2 +-- how much the estimated latency contributes to the final ranking +local WEIGHT_LATENCY = 1 + local function order_server_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 + -- calculate the scores + local s1 = Normalizer:new() + local s2 = Normalizer:new() + for i, fav in ipairs(list) do + -- first: the original position + s1:push(fav, #list - i) + -- second: estimated latency + local ping = (fav.ping or 0) * 1000 + if ping < 400 then + -- If ping is under 400ms replace it with our own estimate, + -- we assume the server has latency issues anyway otherwise + ping = estimate_continent_latency(serverlistmgr.my_continent, fav) or 0 end + s2:push(fav, -ping) end + s1 = s1:calc() + s2 = s2:calc() + + -- make a shallow copy and pre-calculate ordering + local res, order = {}, {} 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 + res[i] = fav + + local n = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY + order[fav] = n end + + -- now sort the list + table.sort(res, function(fav1, fav2) + return order[fav1] > order[fav2] + end) + return res end local public_downloading = false +local geoip_downloading = false -------------------------------------------------------------------------------- function serverlistmgr.sync() @@ -56,6 +129,39 @@ function serverlistmgr.sync() return end + -- only fetched once per MT instance + if not serverlistmgr.my_continent and not geoip_downloading then + geoip_downloading = true + core.handle_async( + function(param) + local http = core.get_http_api() + local url = core.settings:get("serverlist_url") .. "/geoip" + + local response = http.fetch_sync({ url = url }) + if not response.succeeded then + return + end + + local retval = core.parse_json(response.data) + return retval and type(retval.continent) == "string" and retval.continent + end, + nil, + function(result) + geoip_downloading = false + if not result then + return + end + serverlistmgr.my_continent = result + core.set_once("continent", result) + -- reorder list if we already have it + if serverlistmgr.servers then + serverlistmgr.servers = order_server_list(serverlistmgr.servers) + core.event_handler("Refresh") + end + end + ) + end + if public_downloading then return end @@ -79,7 +185,7 @@ function serverlistmgr.sync() end, nil, function(result) - public_downloading = nil + public_downloading = false local favs = order_server_list(result) if favs[1] then serverlistmgr.servers = favs diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua index 899f30bd12c1..ad5f79eaa1b6 100644 --- a/builtin/mainmenu/tab_online.lua +++ b/builtin/mainmenu/tab_online.lua @@ -94,7 +94,7 @@ local function get_formspec(tabview, name, tabdata) -- Name / Password "container[0,4.8]" .. "label[0.25,0;" .. fgettext("Name") .. "]" .. - "label[3,0;" .. fgettext("Password") .. "]" .. + "label[2.875,0;" .. fgettext("Password") .. "]" .. "field[0.25,0.2;2.625,0.75;te_name;;" .. core.formspec_escape(core.settings:get("name")) .. "]" .. "pwdfield[2.875,0.2;2.625,0.75;te_pwd;]" .. "container_end[]" .. diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 21c77aa8eb0a..ec2d7b1f5513 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -308,10 +308,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.show_keys_menu() return true end - if fields["cb_touchscreen_target"] then - core.settings:set("touchtarget", fields["cb_touchscreen_target"]) - return true - end --Note dropdowns have to be handled LAST! local ddhandled = false diff --git a/builtin/mainmenu/tests/serverlistmgr_spec.lua b/builtin/mainmenu/tests/serverlistmgr_spec.lua index ab7a6c60c85e..25e208d100f0 100644 --- a/builtin/mainmenu/tests/serverlistmgr_spec.lua +++ b/builtin/mainmenu/tests/serverlistmgr_spec.lua @@ -1,5 +1,4 @@ -_G.core = {} -_G.vector = {metatable = {}} +_G.core = {get_once = function(_) end} _G.unpack = table.unpack _G.serverlistmgr = {} diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 7376c0cb70f8..e4d345e2d91a 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -117,6 +117,10 @@ mouse_sensitivity (Mouse sensitivity) float 0.2 0.001 10.0 # The length in pixels it takes for touch screen interaction to start. touchscreen_threshold (Touch screen threshold) int 20 0 100 +# Use crosshair to select object instead of whole screen. +# If enabled, a crosshair will be shown and will be used for selecting object. +touch_use_crosshair (Use crosshair for touch screen) bool false + # (Android) Fixes the position of virtual joystick. # If disabled, virtual joystick will center to first-touch's position. fixed_virtual_joystick (Fixed virtual joystick) bool false @@ -445,6 +449,44 @@ shadow_soft_radius (Soft shadow radius) float 5.0 1.0 15.0 # Minimum value: 0.0; maximum value: 60.0 shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0 +[**Post processing] + +# Set the exposure compensation factor. +# This factor is applied to linear color value +# before all other post-processing effects. +# Value of 1.0 (default) means no exposure compensation. +# Range: from 0.1 to 10.0 +exposure_factor (Exposure Factor) float 1.0 0.1 10.0 + +[**Bloom] + +# Set to true to enable bloom effect. +# Bright colors will bleed over the neighboring objects. +enable_bloom (Enable Bloom) bool false + +# Set to true to render debugging breakdown of the bloom effect. +# In debug mode, the screen is split into 4 quadrants: +# top-left - processed base image, top-right - final image +# bottom-left - raw base image, bottom-right - bloom texture. +enable_bloom_debug (Enable Bloom Debug) bool false + +# Set to true to use dedicated texture at each step of bloom effect. +# This is a compatibility setting to avoid visual artifacts +# on certain GPUs and video drivers. +enable_bloom_dedicated_texture (Enable Bloom Dedicated Texture) bool false + +# Set the intensity of bloom +# Smaller values make bloom more subtle +# Range: from 0.01 to 1.0, default: 0.05 +bloom_intensity (Bloom Intensity) float 0.05 0.01 1.0 + +# Set the radius of the bloom filter in pixels. +# Larger values render more glow around bright objects +# at the cost of higher resource consumption. +# Range: from 1 to 64, default: 16 +bloom_radius (Bloom Radius) int 16 1 64 + + [*Audio] # Volume of all sounds. diff --git a/client/shaders/blur_h/opengl_fragment.glsl b/client/shaders/blur_h/opengl_fragment.glsl new file mode 100644 index 000000000000..9aeecad1c0d8 --- /dev/null +++ b/client/shaders/blur_h/opengl_fragment.glsl @@ -0,0 +1,29 @@ +#define rendered texture0 + +uniform sampler2D rendered; +uniform vec2 texelSize0; +uniform mediump float bloomRadius; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + // kernel distance and linear size + mediump float n = 2. * bloomRadius + 1.; + + vec2 uv = varTexCoord.st - vec2(bloomRadius * texelSize0.x, 0.); + vec4 color = vec4(0.); + mediump float sum = 0.; + for (mediump float i = 0.; i < n; i++) { + mediump float weight = pow(1. - (abs(i / bloomRadius - 1.)), 1.3); + color += texture2D(rendered, uv).rgba * weight; + sum += weight; + uv += vec2(texelSize0.x, 0.); + } + color /= sum; + gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. +} diff --git a/client/shaders/blur_h/opengl_vertex.glsl b/client/shaders/blur_h/opengl_vertex.glsl new file mode 100644 index 000000000000..12692c29643f --- /dev/null +++ b/client/shaders/blur_h/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/blur_v/opengl_fragment.glsl b/client/shaders/blur_v/opengl_fragment.glsl new file mode 100644 index 000000000000..5974adf88050 --- /dev/null +++ b/client/shaders/blur_v/opengl_fragment.glsl @@ -0,0 +1,29 @@ +#define rendered texture0 + +uniform sampler2D rendered; +uniform vec2 texelSize0; +uniform mediump float bloomRadius; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + // kernel distance and linear size + mediump float n = 2. * bloomRadius + 1.; + + vec2 uv = varTexCoord.st - vec2(0., bloomRadius * texelSize0.y); + vec4 color = vec4(0.); + mediump float sum = 0.; + for (mediump float i = 0.; i < n; i++) { + mediump float weight = pow(1. - (abs(i / bloomRadius - 1.)), 1.3); + color += texture2D(rendered, uv).rgba * weight; + sum += weight; + uv += vec2(0., texelSize0.y); + } + color /= sum; + gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. +} diff --git a/client/shaders/blur_v/opengl_vertex.glsl b/client/shaders/blur_v/opengl_vertex.glsl new file mode 100644 index 000000000000..12692c29643f --- /dev/null +++ b/client/shaders/blur_v/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/default_shader/opengl_fragment.glsl b/client/shaders/default_shader/opengl_fragment.glsl index 5018ac6ea39d..300c0c5897b8 100644 --- a/client/shaders/default_shader/opengl_fragment.glsl +++ b/client/shaders/default_shader/opengl_fragment.glsl @@ -2,5 +2,5 @@ varying lowp vec4 varColor; void main(void) { - gl_FragColor = varColor; + gl_FragData[0] = varColor; } diff --git a/client/shaders/extract_bloom/opengl_fragment.glsl b/client/shaders/extract_bloom/opengl_fragment.glsl new file mode 100644 index 000000000000..7d18ec26ff20 --- /dev/null +++ b/client/shaders/extract_bloom/opengl_fragment.glsl @@ -0,0 +1,21 @@ +#define rendered texture0 + +uniform sampler2D rendered; +uniform mediump float exposureFactor; +uniform float bloomLuminanceThreshold; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + + +void main(void) +{ + vec2 uv = varTexCoord.st; + vec4 color = texture2D(rendered, uv).rgba; + // translate to linear colorspace (approximate) + color.rgb = pow(color.rgb, vec3(2.2)) * exposureFactor; + gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. +} diff --git a/client/shaders/extract_bloom/opengl_vertex.glsl b/client/shaders/extract_bloom/opengl_vertex.glsl new file mode 100644 index 000000000000..12692c29643f --- /dev/null +++ b/client/shaders/extract_bloom/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index c4b947e72a0e..1f7d98069d7f 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -45,6 +45,9 @@ centroid varying vec2 varTexCoord; #endif varying vec3 eyeVec; varying float nightRatio; +varying vec3 tsEyeVec; +varying vec3 lightVec; +varying vec3 tsLightVec; const float fogStart = FOG_START; const float fogShadingParameter = 1.0 / ( 1.0 - fogStart); @@ -359,40 +362,6 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) #endif #endif -#if 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 main(void) { vec3 color; @@ -470,10 +439,6 @@ void main(void) } #endif -#if 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. @@ -488,5 +453,5 @@ void main(void) col = mix(skyBgColor, col, clarity); col = vec4(col.rgb, base.a); - gl_FragColor = col; + gl_FragData[0] = col; } diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 40f0965e1027..42fae7612a67 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -42,6 +42,7 @@ centroid varying vec2 varTexCoord; varying float perspective_factor; #endif +varying float area_enable_parallax; varying vec3 eyeVec; varying float nightRatio; @@ -193,6 +194,9 @@ void main(void) vPosition = gl_Position.xyz; eyeVec = -(mWorldView * pos).xyz; +#ifdef SECONDSTAGE + normalPass = normalize((inVertexNormal+1)/2); +#endif vNormal = inVertexNormal; // Calculate color. diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 1fefc764b37b..662d0c8659b4 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -361,39 +361,6 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) #endif #endif -#if 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 main(void) { @@ -473,10 +440,6 @@ void main(void) } #endif -#if 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. @@ -491,5 +454,5 @@ void main(void) col = mix(skyBgColor, col, clarity); col = vec4(col.rgb, base.a); - gl_FragColor = col; + gl_FragData[0] = col; } diff --git a/client/shaders/second_stage/opengl_fragment.glsl b/client/shaders/second_stage/opengl_fragment.glsl new file mode 100644 index 000000000000..290caf8e314b --- /dev/null +++ b/client/shaders/second_stage/opengl_fragment.glsl @@ -0,0 +1,100 @@ +#define rendered texture0 +#define bloom texture1 + +uniform sampler2D rendered; +uniform sampler2D bloom; +uniform mediump float exposureFactor; +uniform lowp float bloomIntensity; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +#ifdef ENABLE_BLOOM + +vec4 applyBloom(vec4 color, vec2 uv) +{ + float bias = bloomIntensity; + vec4 bloom = texture2D(bloom, uv); +#ifdef ENABLE_BLOOM_DEBUG + if (uv.x > 0.5 && uv.y < 0.5) + return vec4(bloom.rgb, color.a); + if (uv.x < 0.5) + return color; +#endif + color.rgb = mix(color.rgb, bloom.rgb, bias); + return color; +} + +#endif + +#if 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) +{ + const float exposureBias = 2.0; + 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 color; +} +#endif + +void main(void) +{ + vec2 uv = varTexCoord.st; + vec4 color = texture2D(rendered, uv).rgba; + + // translate to linear colorspace (approximate) + color.rgb = pow(color.rgb, vec3(2.2)); + +#ifdef ENABLE_BLOOM_DEBUG + if (uv.x > 0.5 || uv.y > 0.5) +#endif + { + color.rgb *= exposureFactor; + } + + +#ifdef ENABLE_BLOOM + color = applyBloom(color, uv); +#endif + +#ifdef ENABLE_BLOOM_DEBUG + if (uv.x > 0.5 || uv.y > 0.5) +#endif + { +#if ENABLE_TONE_MAPPING + color = applyToneMapping(color); +#else + color.rgb /= 2.5; // default exposure factor, see also RenderingEngine::DEFAULT_EXPOSURE_FACTOR; +#endif + } + + color.rgb = clamp(color.rgb, vec3(0.), vec3(1.)); + + // return to sRGB colorspace (approximate) + color.rgb = pow(color.rgb, vec3(1.0 / 2.2)); + + gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. +} diff --git a/client/shaders/second_stage/opengl_vertex.glsl b/client/shaders/second_stage/opengl_vertex.glsl new file mode 100644 index 000000000000..12692c29643f --- /dev/null +++ b/client/shaders/second_stage/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/selection_shader/opengl_fragment.glsl b/client/shaders/selection_shader/opengl_fragment.glsl index 35b1f8902dc0..2094ea0f4b38 100644 --- a/client/shaders/selection_shader/opengl_fragment.glsl +++ b/client/shaders/selection_shader/opengl_fragment.glsl @@ -8,5 +8,5 @@ void main(void) vec2 uv = varTexCoord.st; vec4 color = texture2D(baseTexture, uv); color.rgb *= varColor.rgb; - gl_FragColor = color; + gl_FragData[0] = color; } diff --git a/cmake/Modules/FindGettextLib.cmake b/cmake/Modules/FindGettextLib.cmake index b7681827c0ff..4aacc9a40212 100644 --- a/cmake/Modules/FindGettextLib.cmake +++ b/cmake/Modules/FindGettextLib.cmake @@ -1,60 +1,36 @@ - -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") +# This module find everything related to Gettext: +# * development tools (msgfmt) +# * libintl for runtime usage 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) + DOC "Path to Gettext msgfmt") + +if(GETTEXT_INCLUDE_DIR AND GETTEXT_LIBRARY) + # This is only really used on Windows + find_path(GETTEXT_INCLUDE_DIR NAMES libintl.h) + find_library(GETTEXT_LIBRARY NAMES intl) + + set(GETTEXT_REQUIRED_VARS GETTEXT_INCLUDE_DIR GETTEXT_LIBRARY GETTEXT_MSGFMT) +else() + find_package(Intl) + set(GETTEXT_INCLUDE_DIR ${Intl_INCLUDE_DIRS}) + set(GETTEXT_LIBRARY ${Intl_LIBRARIES}) + + # Because intl may be part of the libc it's valid for the two variables to + # be empty, therefore we can't just put them into GETTEXT_REQUIRED_VARS. + if(Intl_FOUND) + set(GETTEXT_REQUIRED_VARS GETTEXT_MSGFMT) else() - set(GETTEXT_LIB_NAMES - libintl.dll.a intl.dll.a libintl3.dll.a intl3.dll.a) + set(GETTEXT_REQUIRED_VARS _LIBINTL_WAS_NOT_FOUND) endif() - find_library(GETTEXT_LIBRARY - NAMES ${GETTEXT_LIB_NAMES} - PATHS "${CUSTOM_GETTEXT_PATH}/lib" - DOC "GetText library") -endif(WIN32) - +endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GettextLib DEFAULT_MSG ${GETTEXT_REQUIRED_VARS}) - if(GETTEXTLIB_FOUND) - # BSD variants require special linkage as they don't use glibc - if(${CMAKE_SYSTEM_NAME} MATCHES "BSD|DragonFly") - set(GETTEXT_LIBRARY "intl") - endif() - + # Set up paths for building set(GETTEXT_PO_PATH ${CMAKE_SOURCE_DIR}/po) set(GETTEXT_MO_BUILD_PATH ${CMAKE_BINARY_DIR}/locale//LC_MESSAGES) set(GETTEXT_MO_DEST_PATH ${LOCALEDIR}//LC_MESSAGES) diff --git a/doc/breakages.md b/doc/breakages.md index f625a0f4d7d3..9f59b9705dd6 100644 --- a/doc/breakages.md +++ b/doc/breakages.md @@ -3,6 +3,7 @@ This document contains a list of breaking changes to be made in the next major version. * Remove attachment space multiplier (*10) +* Remove player gravity multiplier (*2) * `get_sky()` returns a table (without arg) * `game.conf` name/id mess * remove `depends.txt` / `description.txt` (would simplify ContentDB and Minetest code a little) diff --git a/doc/builtin_entities.txt b/doc/builtin_entities.txt index be3f7335725d..18ff0e0b64ca 100644 --- a/doc/builtin_entities.txt +++ b/doc/builtin_entities.txt @@ -10,15 +10,17 @@ artificially with `minetest.spawn_falling_node`. Needs manual initialization when spawned using `/spawnentity`. -Default behaviour: +Default behavior: * Falls down in a straight line (gravity = `movement_gravity` setting) * Collides with `walkable` node * Collides with all physical objects except players * If the node group `float=1` is set, it also collides with liquid nodes + (nodes with `liquidtype ~= "none"`) * When it hits a solid (=`walkable`) node, it will try to place itself as a node, replacing the node above. - * If the falling node cannot replace the destination node, it is dropped. + * If the falling node cannot replace the destination node, it is dropped + as an item. * If the destination node is a leveled node (`paramtype2="leveled"`) of the same node name, the levels of both are summed. @@ -57,13 +59,18 @@ Supported drawtypes: * `airlike` (not pointable) Other drawtypes still kinda work, but they might look weird. +If the node uses a world-aligned texture with a `scale` greater +than 1, the falling node will display the top-most, left-most +portion of that texture. Supported `paramtype2` values: * `wallmounted` * `facedir` +* `4dir` * `colorwallmounted` * `colorfacedir` +* `color4dir` * `color` ## Dropped item stack (`__builtin:item`) @@ -94,7 +101,7 @@ Needs manual initialization when spawned using `/spawnentity`. * `set_item(self, item)`: * Function to initialize the dropped item * `item` (type `ItemStack`) specifies the item to represent -* `age`: Age in seconds. Behaviour according to the setting `item_entity_ttl` +* `age`: Age in seconds. Behavior according to the setting `item_entity_ttl` * `itemstring`: Itemstring of the item that this item entity represents. Read-only. diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index c789e8ca38f4..252f20fb2de1 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -342,7 +342,7 @@ examples. #### `bgcolor[;]` * Sets background color of formspec as `ColorString` -* If `true`, the background color is drawn fullscreen (does not effect the size of the formspec) +* If `true`, the background color is drawn fullscreen (does not affect the size of the formspec) #### `background[,;,;]` * Use a background. Inventory rectangles are not drawn then. @@ -364,7 +364,7 @@ examples. 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` +* Fields are a set height, but will be vertically centered 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 @@ -376,7 +376,7 @@ examples. 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` +* Fields are a set height, but will be vertically centered 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 @@ -418,7 +418,7 @@ examples. * 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 -* Fixed button height. It will be vertically centred on `h` +* Fixed button height. It will be vertically centered on `h` * `label` is the text on the button * Position and size units are inventory slots @@ -565,7 +565,7 @@ examples. * `color` column options: * `span=`: number of following columns to affect (default: infinite) -**Note**: do _not_ use a element name starting with `key_`; those names are reserved to +**Note**: do _not_ use an element name starting with `key_`; those names are reserved to pass key press events to formspec! Spatial Vectors @@ -684,7 +684,7 @@ Call these functions only at load time! * 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(function(message))` - * Called always when a client send a message from chat + * Called always when a client sends 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 @@ -692,7 +692,7 @@ Call these functions only at load time! * Unregisters a chatcommands registered with register_chatcommand. * `minetest.register_on_chatcommand(function(command, params))` * Called always when a chatcommand is triggered, before `minetest.registered_chatcommands` - is checked to see if that the command exists, but after the input is parsed. + is checked to see if the command exists, but after the input is parsed. * Return `true` to mark the command as handled, which means that the default handlers will be prevented. * `minetest.register_on_death(function())` @@ -879,7 +879,7 @@ Call these functions only at load time! * 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. + 2. You cannot 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 @@ -897,16 +897,21 @@ Call these functions only at load time! * 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`. + * Deflate (zlib): `"deflate"` + * Zstandard: `"zstd"` + * `...` indicates method-specific arguments. Currently defined arguments + are: + * Deflate: `level` - Compression level, `0`-`9` or `nil`. + * Zstandard: `level` - Compression level. Integer or `nil`. Default `3`. + Note any supported Zstandard compression level could be used here, + but these are subject to change between Zstandard versions. * `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. + * Decompress a string of data using the algorithm specified by `method`. + * See documentation on `minetest.compress()` for supported compression + methods. + * `...` indicates method-specific arguments. Currently, no methods use this * `minetest.rgba(red, green, blue[, alpha])`: returns a string - * Each argument is a 8 Bit unsigned integer + * Each argument is an 8 Bit unsigned integer * Returns the ColorString from rgb or rgba values * Example: `minetest.rgba(10, 20, 30, 40)`, returns `"#0A141E28"` * `minetest.encode_base64(string)`: returns string encoded in base64 @@ -1211,7 +1216,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or paramtype = string, -- Paramtype of the node paramtype2 = string, -- ParamType2 of the node drawtype = string, -- Drawtype of the node - mesh = , -- Mesh name if existant + mesh = , -- Mesh name if existent minimap_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 @@ -1331,7 +1336,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or Escape sequences ---------------- -Most text can contain escape sequences, that can for example color the text. +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)`: diff --git a/doc/direction.md b/doc/direction.md index 826dd47b3b86..72e40da250cc 100644 --- a/doc/direction.md +++ b/doc/direction.md @@ -19,10 +19,10 @@ These goals were created from the top points in a [roadmap brainstorm](https://github.com/minetest/minetest/issues/10461). This should be reviewed approximately yearly, or when goals are achieved. -Pull requests that address one of these goals will be labelled as "Roadmap". +Pull requests that address one of these goals will be labeled as "Roadmap". PRs that are not on the roadmap will be closed unless they receive a concept approval within a week, issues can be used for preapproval. -Bug fixes are exempt for this, and are always accepted and prioritised. +Bug fixes are exempt for this, and are always accepted and prioritized. See [CONTRIBUTING.md](../.github/CONTRIBUTING.md) for more info. ### 2.1 Rendering/Graphics improvements diff --git a/doc/fst_api.txt b/doc/fst_api.txt index 6f9aa14b3230..294642cc3f17 100644 --- a/doc/fst_api.txt +++ b/doc/fst_api.txt @@ -59,7 +59,7 @@ methods: 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" + on_change = function(type,old_tab,new_tab) -- called on tab change, type is "ENTER" or "LEAVE" } - set_autosave_tab(value) ^ tell tabview to automatically save current tabname as "tabview_name"_LAST diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9403de670253..fd4e2d3e5af9 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -396,7 +396,7 @@ Deprecated, define dungeon nodes in biome definitions instead. * `mapgen_stair_cobble` (falls back to cobble) * `mapgen_mossycobble` (falls back to cobble) -* `mapgen_stair_desert_stone` (falls backto desert_stone) +* `mapgen_stair_desert_stone` (falls back to desert_stone) ### Setting the node used in Mapgen Singlenode @@ -758,6 +758,17 @@ appropriate `paramtype2`: 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. +* `paramtype2 = "color4dir"` for nodes which use the first + six bits of `param2` for palette indexing. The remaining + two bits are describing rotation, as in `4dir` paramtype2. + Division by 4 yields the palette index (without stretching the + palette). These nodes can have 64 different colors, and the + palette should contain 64 pixels. + Examples: + * `param2 = 17` is 4 * 4 + 1, so the rotation is 1 and the + fifth (= 4 + 1) pixel will be picked from the palette. + * `param2 = 35` is 8 * 4 + 3, so the rotation is 3 and the + ninth (= 8 + 1) pixel will be picked from the palette. To colorize a node on the map, set its `param2` value (according to the node's paramtype2). @@ -908,13 +919,13 @@ Examples of sound parameter tables: { pos = {x = 1, y = 2, z = 3}, gain = 1.0, -- default - max_hear_distance = 32, -- default, uses an euclidean metric + max_hear_distance = 32, -- default, uses a Euclidean metric } -- Play connected to an object, looped { object = , gain = 1.0, -- default - max_hear_distance = 32, -- default, uses an euclidean metric + max_hear_distance = 32, -- default, uses a Euclidean metric loop = true, } -- Play at a location, heard by anyone *but* the given player @@ -983,7 +994,7 @@ existence before trying to access the fields. Example: -All nodes register with `minetest.register_node` get added to the table +All nodes registered with `minetest.register_node` get added to the table `minetest.registered_nodes`. If you want to check the drawtype of a node, you could do it like this: @@ -1058,18 +1069,39 @@ The function of `param2` is determined by `paramtype2` in node definition. * Supported drawtypes: "torchlike", "signlike", "plantlike", "plantlike_rooted", "normal", "nodebox", "mesh" * The rotation of the node is stored in `param2` + * Node is 'mounted'/facing towards one of 6 directions * You can make this value by using `minetest.dir_to_wallmounted()` * Values range 0 - 5 * The value denotes at which direction the node is "mounted": 0 = y+, 1 = y-, 2 = x+, 3 = x-, 4 = z+, 5 = z- + * By default, on placement the param2 is automatically set to the + appropriate rotation, depending on which side was pointed at * `paramtype2 = "facedir"` * Supported drawtypes: "normal", "nodebox", "mesh" - * 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()`. + * The rotation of the node is stored in `param2`. + * Node is rotated around face and axis; 24 rotations in total. + * Can be made by using `minetest.dir_to_facedir()`. + * Chests and furnaces can be rotated that way, and also 'flipped' * 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 + * The node is rotated 90 degrees around the X or Z axis so that its top face + points in the desired direction. For the y- direction, it's rotated 180 + degrees around the Z axis. + * facedir modulo 4 = left-handed rotation around the specified axis, in 90° steps. + * By default, on placement the param2 is automatically set to the + horizontal direction the player was looking at (values 0-3) + * Special case: If the node is a connected nodebox, the nodebox + will NOT rotate, only the textures will. +* `paramtype2 = "4dir"` + * Supported drawtypes: "normal", "nodebox", "mesh" + * The rotation of the node is stored in `param2`. + * Allows node to be rotated horizontally, 4 rotations in total + * Can be made by using `minetest.dir_to_fourdir()`. + * Chests and furnaces can be rotated that way, but not flipped + * Values range 0 - 3 + * 4dir modulo 4 = rotation + * Otherwise, behavior is identical to facedir * `paramtype2 = "leveled"` * Only valid for "nodebox" with 'type = "leveled"', and "plantlike_rooted". * Leveled nodebox: @@ -1091,7 +1123,7 @@ The function of `param2` is determined by `paramtype2` in node definition. optional modifiers of the "plant". `param2` is a bitfield. * Bits 0 to 2 select the shape. Use only one of the values below: - * 0 = a "x" shaped plant (ordinary plant) + * 0 = an "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 @@ -1112,6 +1144,10 @@ The function of `param2` is determined by `paramtype2` in node definition. * 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 = "color4dir"` + * Same as `facedir`, but with colors. + * The first six bits of `param2` tells which color is picked from the + palette. The palette should have 64 pixels. * `paramtype2 = "colorwallmounted"` * Same as `wallmounted`, but with colors. * The first five bits of `param2` tells which color is picked from the @@ -1153,12 +1189,14 @@ Look for examples in `games/devtest` or `games/minetest_game`. * The cubic source node for a liquid. * Faces bordering to the same node are never rendered. * Connects to node specified in `liquid_alternative_flowing`. + * You *must* set `liquid_alternative_source` to the node's own name. * Use `backface_culling = false` for the tiles you want to make visible when inside the node. * `flowingliquid` * The flowing version of a liquid, appears with various heights and slopes. * Faces bordering to the same node are never rendered. * Connects to node specified in `liquid_alternative_source`. + * You *must* set `liquid_alternative_flowing` to the node's own name. * Node textures are defined with `special_tiles` where the first tile is for the top and bottom faces and the second tile is for the side faces. @@ -1285,7 +1323,7 @@ A nodebox is defined as any of: wall_side = box } { - -- A node that has optional boxes depending on neighbouring nodes' + -- A node that has optional boxes depending on neighboring nodes' -- presence and type. See also `connects_to`. type = "connected", fixed = box OR {box1, box2, ...} @@ -1296,7 +1334,7 @@ A nodebox is defined as any of: connect_back = box OR {box1, box2, ...} connect_right = box OR {box1, box2, ...} -- The following `disconnected_*` boxes are the opposites of the - -- `connect_*` ones above, i.e. when a node has no suitable neighbour + -- `connect_*` ones above, i.e. when a node has no suitable neighbor -- on the respective side, the corresponding disconnected box is drawn. disconnected_top = box OR {box1, box2, ...} disconnected_bottom = box OR {box1, box2, ...} @@ -1304,9 +1342,9 @@ A nodebox is defined as any of: disconnected_left = box OR {box1, box2, ...} disconnected_back = box OR {box1, box2, ...} disconnected_right = box OR {box1, box2, ...} - disconnected = box OR {box1, box2, ...} -- when there is *no* neighbour + disconnected = box OR {box1, box2, ...} -- when there is *no* neighbor disconnected_sides = box OR {box1, box2, ...} -- when there are *no* - -- neighbours to the sides + -- neighbors to the sides } A `box` is defined as: @@ -1341,7 +1379,7 @@ clients and handled by many parts of the engine. A 'mapchunk' (sometimes abbreviated to 'chunk') is usually 5x5x5 mapblocks (80x80x80 nodes) and is the volume of world generated in one operation by the map generator. -The size in mapblocks has been chosen to optimise map generation. +The size in mapblocks has been chosen to optimize map generation. Coordinates ----------- @@ -1626,7 +1664,7 @@ Item types There are three kinds of items: nodes, tools and craftitems. * Node: Placeable item form of a node in the world's voxel grid -* Tool: Has a changable wear property but cannot be stacked +* Tool: Has a changeable wear property but cannot be stacked * Craftitem: Has no special properties Every registered node (the voxel in the world) has a corresponding @@ -1694,7 +1732,7 @@ Examples: * amount must be 1 (pickaxe is a tool), ca. 1/3 worn out (it's a tool), * with the `description` field set to `"My worn out pick"` in its metadata * `[[default:dirt 5 0 "\u0001description\u0002Special dirt\u0003"]]`: - * analogeous to the above example + * analogous to the above example * note how the wear is set to `0` as dirt is not a tool You should ideally use the `ItemStack` format to build complex item strings @@ -1783,8 +1821,8 @@ For entities, groups are, as of now, used only for calculating damage. The rating is the percentage of damage caused by items 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}) + object:get_armor_groups() --> a group-rating table (e.g. {fleshy=100}) + object:set_armor_groups({fleshy=30, cracky=80}) Groups of tool capabilities --------------------------- @@ -1795,7 +1833,24 @@ are effective towards. Groups in crafting recipes -------------------------- -An example: Make meat soup from any meat, any water and any bowl: +In crafting recipes, you can specify a group as an input item. +This means that any item in that group will be accepted as input. + +The basic syntax is: + + "group:" + +For example, `"group:meat"` will accept any item in the `meat` group. + +It is also possible to require an input item to be in +multiple groups at once. The syntax for that is: + + "group:,,(...)," + +For example, `"group:leaves,birch,trimmed"` accepts any item which is member +of *all* the groups `leaves` *and* `birch` *and* `trimmed`. + +An example recipe: Craft a raw meat soup from any meat, any water and any bowl: { output = "food:meat_soup_raw", @@ -1806,7 +1861,9 @@ An example: Make meat soup from any meat, any water and any bowl: }, } -Another example: Make red wool from white wool and red dye: +Another example: Craft red wool from white wool and red dye +(here, "red dye" is defined as any item which is member of +*both* the groups `dye` and `basecolor_red`). { type = "shapeless", @@ -1832,7 +1889,9 @@ to games. * `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. -* `bouncy`: value is bounce speed in percent +* `bouncy`: value is bounce speed in percent. + If positive, jump/sneak on floor impact will increase/decrease bounce height. + Negative value is the same bounciness, but non-controllable. * `connect_to_raillike`: makes nodes of raillike drawtype with same group value connect to each other * `dig_immediate`: Player can always pick up node without reducing tool wear @@ -2037,7 +2096,7 @@ Example definition of the capabilities of an item }, } -This makes the item capable of digging nodes that fulfil both of these: +This makes the item capable of digging nodes that fulfill both of these: * Have the `crumbly` group * Have a `level` group less or equal to `2` @@ -2260,7 +2319,7 @@ For colored text you can use `minetest.colorize`. Since formspec version 3, elements drawn in the order they are defined. All background elements are drawn before all other elements. -**WARNING**: do _not_ use a element name starting with `key_`; those names are +**WARNING**: do _not_ use an element name starting with `key_`; those names are reserved to pass key press events to formspec! **WARNING**: Minetest allows you to add elements to every single formspec instance @@ -2562,7 +2621,7 @@ Elements * When enter is pressed in field, fields.key_enter_field will be sent with the name of this field. * With the old coordinate system, fields are a set height, but will be vertically - centred on `H`. With the new coordinate system, `H` will modify the height. + centered on `H`. With the new coordinate system, `H` will modify the height. * `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 @@ -2573,7 +2632,7 @@ Elements * When enter is pressed in field, `fields.key_enter_field` will be sent with the name of this field. * With the old coordinate system, fields are a set height, but will be vertically - centred on `H`. With the new coordinate system, `H` will modify the height. + centered on `H`. With the new coordinate system, `H` will modify the height. * `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 @@ -2636,7 +2695,7 @@ Elements * Clickable button. When clicked, fields will be sent. * With the old coordinate system, buttons are a set height, but will be vertically - centred on `H`. With the new coordinate system, `H` will modify the height. + centered on `H`. With the new coordinate system, `H` will modify the height. * `label` is the text on the button ### `image_button[,;,;;;