diff --git a/.gitignore b/.gitignore index e3212f50..48f49ee0 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ src/cmake_config_githash.h src/lua/build/ locale/ .directory +.gradle/ *.cbp *.layout *.o @@ -84,6 +85,8 @@ locale/ *.ninja .ninja* *.gch +*.iml +test_config.h cmake-build-debug/ cmake-build-release/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c17486a..4d5cc07f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,12 +16,13 @@ set(CMAKE_CXX_STANDARD 11) set(VERSION_MAJOR 1) set(VERSION_MINOR 1) set(VERSION_PATCH 10) +set(VERSION_TWEAK 0) set(VERSION_EXTRA "" 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}") +set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}") if(VERSION_EXTRA) set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA}) elseif(DEVELOPMENT_BUILD) diff --git a/build/WindowsApp/buildwin32.sh b/build/WindowsApp/buildwin32.sh old mode 100755 new mode 100644 index 29c23452..d9e28071 --- a/build/WindowsApp/buildwin32.sh +++ b/build/WindowsApp/buildwin32.sh @@ -139,4 +139,7 @@ cmake .. \ make package -j $(nproc) +[ "x$NO_PACKAGE" = "x" ] && make package + +exit 0 # EOF diff --git a/build/WindowsApp/buildwin64.sh b/build/WindowsApp/buildwin64.sh old mode 100755 new mode 100644 index 4e49333b..3ece21a4 --- a/build/WindowsApp/buildwin64.sh +++ b/build/WindowsApp/buildwin64.sh @@ -139,4 +139,7 @@ cmake .. \ make package -j $(nproc) +[ "x$NO_PACKAGE" = "x" ] && make package + +exit 0 # EOF diff --git a/build/android/build.gradle b/build/android/build.gradle index 533b32c5..dc508cfc 100644 --- a/build/android/build.gradle +++ b/build/android/build.gradle @@ -17,13 +17,13 @@ allprojects { apply plugin: 'com.android.application' android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 28 + buildToolsVersion "28.0.3" defaultConfig { applicationId "mobi.MultiCraft" minSdkVersion 16 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 92 } Properties props = new Properties() @@ -73,5 +73,5 @@ android.applicationVariants.all { variant -> } dependencies { - implementation 'com.android.support:support-compat:27.1.1' + implementation 'com.android.support:support-compat:28.0.0' } diff --git a/build/android/patches/irrlicht-native_activity.patch b/build/android/patches/irrlicht-native_activity.patch index 74a6f5e4..573702b7 100644 --- a/build/android/patches/irrlicht-native_activity.patch +++ b/build/android/patches/irrlicht-native_activity.patch @@ -1,12 +1,13 @@ ---- irrlicht/source/Irrlicht/CEGLManager.cpp.orig 2018-04-24 19:27:51.034727946 +0200 -+++ irrlicht/source/Irrlicht/CEGLManager.cpp 2018-04-24 19:27:55.084614618 +0200 -@@ -8,6 +8,9 @@ - - #include "irrString.h" - #include "os.h" -+#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) -+#include -+#endif - - namespace irr - { +--- 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 ++#endif ++ + namespace irr + { + namespace video diff --git a/build/android/src/main/java/net.minetest.minetest/MainActivity.java b/build/android/src/main/java/net.minetest.minetest/MainActivity.java new file mode 100644 index 00000000..1baa7166 --- /dev/null +++ b/build/android/src/main/java/net.minetest.minetest/MainActivity.java @@ -0,0 +1,79 @@ +package net.minetest.minetest; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat;; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MainActivity extends Activity { + + private final static int PERMISSIONS = 1; + private static final String[] REQUIRED_SDK_PERMISSIONS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + checkPermission(); + } else { + next(); + } + } + + protected void checkPermission() { + final List missingPermissions = new ArrayList(); + // check required permission + for (final String permission : REQUIRED_SDK_PERMISSIONS) { + final int result = ContextCompat.checkSelfPermission(this, permission); + if (result != PackageManager.PERMISSION_GRANTED) { + missingPermissions.add(permission); + } + } + if (!missingPermissions.isEmpty()) { + // request permission + final String[] permissions = missingPermissions + .toArray(new String[missingPermissions.size()]); + ActivityCompat.requestPermissions(this, permissions, PERMISSIONS); + } else { + final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length]; + Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED); + onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS, + grantResults); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], + @NonNull int[] grantResults) { + switch (requestCode) { + case PERMISSIONS: + for (int index = 0; index < permissions.length; index++) { + if (grantResults[index] != PackageManager.PERMISSION_GRANTED) { + // permission not granted - toast and exit + Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show(); + finish(); + return; + } + } + // permission were granted - run + next(); + break; + } + } + + public void next() { + Intent intent = new Intent(this, MtNativeActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } +} diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 68481f7c..aad5f2d2 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -120,7 +120,12 @@ end -- The dumped and level arguments are internal-only. function dump(o, indent, nested, level) - if type(o) ~= "table" then + 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 @@ -308,59 +313,25 @@ function core.formspec_escape(text) end -function core.wrap_text(text, charlimit) - local retval = {} - - local current_idx = 1 - - local start,stop = string_find(text, " ", current_idx) - local nl_start,nl_stop = string_find(text, "\n", current_idx) - local gotnewline = false - if nl_start ~= nil and (start == nil or nl_start < start) then - start = nl_start - stop = nl_stop - gotnewline = true - end - local last_line = "" - while start ~= nil do - if string.len(last_line) + (stop-start) > charlimit then - retval[#retval + 1] = last_line - last_line = "" - end - - if last_line ~= "" then - last_line = last_line .. " " - end - - last_line = last_line .. string_sub(text, current_idx, stop - 1) - - if gotnewline then - retval[#retval + 1] = last_line - last_line = "" - gotnewline = false - end - current_idx = stop+1 - - start,stop = string_find(text, " ", current_idx) - nl_start,nl_stop = string_find(text, "\n", current_idx) - - if nl_start ~= nil and (start == nil or nl_start < start) then - start = nl_start - stop = nl_stop - gotnewline = true - end +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 - --add last part of text - if string.len(last_line) + (string.len(text) - current_idx) > charlimit then - retval[#retval + 1] = last_line - retval[#retval + 1] = string_sub(text, current_idx) - else - last_line = last_line .. " " .. string_sub(text, current_idx) - retval[#retval + 1] = last_line + 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 - return retval + table.insert(result, table.concat(line, ' ')) + return as_table and result or table.concat(result, '\n') end -------------------------------------------------------------------------------- @@ -370,7 +341,7 @@ if INIT == "game" then local dirs2 = {20, 23, 22, 21} function core.rotate_and_place(itemstack, placer, pointed_thing, - infinitestacks, orient_flags) + infinitestacks, orient_flags, prevent_after_place) orient_flags = orient_flags or {} local unode = core.get_node_or_nil(pointed_thing.under) @@ -379,41 +350,20 @@ if INIT == "game" then end local undef = core.registered_nodes[unode.name] if undef and undef.on_rightclick then - undef.on_rightclick(pointed_thing.under, unode, placer, + return undef.on_rightclick(pointed_thing.under, unode, placer, itemstack, pointed_thing) - return end - local fdir = core.dir_to_facedir(placer:get_look_dir()) - local wield_name = itemstack:get_name() + 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) - local anode = core.get_node_or_nil(above) - if not anode then - return - end - local pos = pointed_thing.above - local node = anode if undef and undef.buildable_to then - pos = pointed_thing.under - node = unode iswall = false end - if core.is_protected(pos, placer:get_player_name()) then - core.record_protection_violation(pos, - placer:get_player_name()) - return - end - - local ndef = core.registered_nodes[node.name] - if not ndef or not ndef.buildable_to then - return - end - if orient_flags.force_floor then iswall = false isceiling = false @@ -427,31 +377,26 @@ if INIT == "game" then iswall = not iswall end + local param2 = fdir if iswall then - core.set_node(pos, {name = wield_name, - param2 = dirs1[fdir + 1]}) + param2 = dirs1[fdir + 1] elseif isceiling then if orient_flags.force_facedir then - core.set_node(pos, {name = wield_name, - param2 = 20}) + cparam2 = 20 else - core.set_node(pos, {name = wield_name, - param2 = dirs2[fdir + 1]}) + param2 = dirs2[fdir + 1] end else -- place right side up if orient_flags.force_facedir then - core.set_node(pos, {name = wield_name, - param2 = 0}) - else - core.set_node(pos, {name = wield_name, - param2 = fdir}) + param2 = 0 end end - if not infinitestacks then - itemstack:take_item() - return itemstack - 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 @@ -459,12 +404,18 @@ if INIT == "game" then --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, - core.settings:get_bool("creative_mode"), - {invert_wall = placer:get_player_control().sneak}) + is_creative(name), + {invert_wall = invert_wall}, true) return itemstack end end @@ -642,44 +593,26 @@ end local ESCAPE_CHAR = string.char(0x1b) --- Client-side mods don't have access to settings -if core.settings and core.settings:get_bool("disable_escape_sequences") then - - function core.get_color_escape_sequence(color) - return "" - end - - function core.get_background_escape_sequence(color) - return "" - end - - function core.colorize(color, message) - return message - end - -else - - 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.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 diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua index 8cb4ebf5..19af8db7 100644 --- a/builtin/game/auth.lua +++ b/builtin/game/auth.lua @@ -67,16 +67,15 @@ local function save_auth_file() assert(type(stuff.privileges) == "table") assert(stuff.last_login == nil or type(stuff.last_login) == "number") end - local file, errmsg = io.open(core.auth_file_path, 'w+b') - if not file then - error(core.auth_file_path.." could not be opened for writing: "..errmsg) - end + 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 ""} - file:write(table.concat(parts, ":").."\n") + 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 - io.close(file) end read_auth_file() diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 3fc8a845..916db63a 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -653,8 +653,8 @@ core.register_chatcommand("pulverize", { core.rollback_punch_callbacks = {} core.register_on_punchnode(function(pos, node, puncher) - local name = puncher:get_player_name() - if core.rollback_punch_callbacks[name] then + 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 @@ -814,7 +814,7 @@ core.register_chatcommand("shutdown", { message = message or "" if delay ~= "" then - delay = tonumber(param) or 0 + delay = tonumber(delay) or 0 else delay = 0 core.log("action", name .. " shuts down server") diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index b1beb1ab..991962cc 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -60,8 +60,13 @@ core.register_entity(":__builtin:falling_node", { local pos = self.object:getpos() -- Position of bottom center point local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z} - -- Avoid bugs caused by an unloaded node below + -- '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 @@ -93,7 +98,7 @@ core.register_entity(":__builtin:falling_node", { core.remove_node(np) if nd and nd.buildable_to == false then -- Add dropped items - local drops = core.get_node_drops(n2.name, "") + local drops = core.get_node_drops(n2, "") for _, dropped_item in pairs(drops) do core.add_item(np, dropped_item) end @@ -145,9 +150,9 @@ function core.spawn_falling_node(pos) end local function drop_attached_node(p) - local nn = core.get_node(p).name + local n = core.get_node(p) core.remove_node(p) - for _, item in pairs(core.get_node_drops(nn, "")) do + 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, diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 3447a564..b595714e 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -155,11 +155,45 @@ function core.yaw_to_dir(yaw) return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)} end -function core.get_node_drops(nodename, toolname) +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 @@ -181,6 +215,8 @@ function core.get_node_drops(nodename, toolname) 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 @@ -191,10 +227,16 @@ function core.get_node_drops(nodename, toolname) break end 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 @@ -205,7 +247,22 @@ function core.get_node_drops(nodename, toolname) return got_items end -function core.item_place_node(itemstack, placer, pointed_thing, param2) +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 @@ -215,10 +272,11 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) 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 = placer:get_player_name() + local playername = user_name(placer) + local log = make_log(playername) if not oldnode_under or not oldnode_above then - core.log("info", playername .. " tried to place" + log("info", playername .. " tried to place" .. " node in unloaded position " .. core.pos_to_string(above)) return itemstack, false end @@ -229,7 +287,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) olddef_above = olddef_above or core.nodedef_default if not olddef_above.buildable_to and not olddef_under.buildable_to then - core.log("info", playername .. " tried to place" + log("info", playername .. " tried to place" .. " node in invalid position " .. core.pos_to_string(above) .. ", replacing " .. oldnode_above.name) return itemstack, false @@ -240,13 +298,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) -- If node under is buildable_to, place into it instead (eg. snow) if olddef_under.buildable_to then - core.log("info", "node under is buildable to") + log("info", "node under is buildable to") place_to = {x = under.x, y = under.y, z = under.z} end - if core.is_protected(place_to, playername) and - not minetest.check_player_privs(placer, "protection_bypass") then - core.log("action", playername + if is_protected(place_to, playername) then + log("action", playername .. " tried to place " .. def.name .. " at protected position " .. core.pos_to_string(place_to)) @@ -254,11 +311,11 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) return itemstack end - core.log("action", playername .. " places node " + 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} + 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 @@ -274,7 +331,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) -- 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:getpos() + local placer_pos = placer and placer:getpos() if placer_pos then local dir = { x = above.x - placer_pos.x, @@ -282,14 +339,33 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) z = above.z - placer_pos.z } newnode.param2 = core.dir_to_facedir(dir) - core.log("action", "facedir: " .. newnode.param2) + 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 - core.log("action", "attached node " .. def.name .. + log("action", "attached node " .. def.name .. " can not be placed at " .. core.pos_to_string(place_to)) return itemstack, false end @@ -300,7 +376,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) local take_item = true -- Run callback - if def.after_place_node then + 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) @@ -360,28 +436,27 @@ function core.item_secondary_use(itemstack, placer) end function core.item_drop(itemstack, dropper, pos) - if dropper and dropper:is_player() then - local v = dropper:get_look_dir() - local p = {x=pos.x, y=pos.y+1.2, z=pos.z} - local cs = itemstack:get_count() + 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 - cs = 1 + cnt = 1 end - local item = itemstack:take_item(cs) - local obj = core.add_item(p, item) - if obj then - v.x = v.x*2 - v.y = v.y*2 + 2 - v.z = v.z*2 - obj:setvelocity(v) + end + 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() - return itemstack - end - - else - if core.add_item(pos, itemstack) then - return itemstack end + return itemstack end -- If we reach this, adding the object to the -- environment failed @@ -402,7 +477,8 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed itemstack:add_item(replace_with_item) else local inv = user:get_inventory() - if inv:room_for_item("main", {name=replace_with_item}) then + -- 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() @@ -417,7 +493,9 @@ end function core.item_eat(hp_change, replace_with_item) return function(itemstack, user, pointed_thing) -- closure - return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing) + if user then + return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing) + end end end @@ -434,63 +512,76 @@ end function core.handle_node_drops(pos, drops, digger) -- Add dropped items to object's inventory - if digger:get_inventory() then - local _, dropped_item - for _, dropped_item in ipairs(drops) do - local left = digger:get_inventory():add_item("main", dropped_item) - if not left:is_empty() then - local p = { - x = pos.x + math.random()/2-0.25, - y = pos.y + math.random()/2-0.25, - z = pos.z + math.random()/2-0.25, - } - core.add_item(p, left) - end + 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 - core.log("info", digger:get_player_name() .. " tried to dig " + log("info", diggername .. " tried to dig " .. node.name .. " which is not diggable " .. core.pos_to_string(pos)) return end - if core.is_protected(pos, digger:get_player_name()) and - not minetest.check_player_privs(digger, "protection_bypass") then - core.log("action", digger:get_player_name() + 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, digger:get_player_name()) + core.record_protection_violation(pos, diggername) return end - core.log('action', digger:get_player_name() .. " digs " + log('action', diggername .. " digs " .. node.name .. " at " .. core.pos_to_string(pos)) - local wielded = digger:get_wielded_item() - local drops = core.get_node_drops(node.name, wielded:get_name()) + local wielded = digger and digger:get_wielded_item() + local drops = core.get_node_drops(node, wielded and wielded:get_name()) - 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}) + 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 - digger:set_wielded_item(wielded) -- Handle drops core.handle_node_drops(pos, drops, digger) diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index 74b499e5..bae19b33 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -174,19 +174,18 @@ core.register_entity(":__builtin:item", { local p = self.object:getpos() p.y = p.y - 0.5 local node = core.get_node_or_nil(p) - local in_unloaded = (node == nil) - if in_unloaded then - -- Don't infinetly fall into unloaded map - 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}) + -- Delete in 'ignore' nodes + if node and node.name == "ignore" then + self.itemstring = "" + self.object:remove() return end - local nn = node.name - -- If node is not registered or node is walkably solid and resting on nodebox + + -- 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 core.registered_nodes[nn] or core.registered_nodes[nn].walkable and v.y == 0 then + 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 diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index bfe407b9..d8f7a638 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -5,12 +5,11 @@ -- function core.check_player_privs(name, ...) - local arg_type = type(name) - if (arg_type == "userdata" or arg_type == "table") and - name.get_player_name then -- If it quacks like a Player... + if core.is_player(name) then name = name:get_player_name() - elseif arg_type ~= "string" then - error("Invalid core.check_player_privs argument type: " .. arg_type, 2) + elseif type(name) ~= "string" then + error("core.check_player_privs expects a player or playername as " .. + "argument.", 2) end local requested_privs = {...} @@ -70,6 +69,16 @@ function core.get_connected_players() 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 diff --git a/builtin/game/register.lua b/builtin/game/register.lua index ec6f2809..25af24eb 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -116,6 +116,8 @@ function core.register_item(name, itemdef) 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 @@ -177,7 +179,13 @@ function core.register_item(name, itemdef) --core.log("Registering item: " .. itemdef.name) core.registered_items[itemdef.name] = itemdef core.registered_aliases[itemdef.name] = nil - register_item_raw(itemdef) + + -- 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) diff --git a/builtin/init.lua b/builtin/init.lua index 356e119f..73ab5cfd 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -21,7 +21,6 @@ if core.print then core.print = nil -- don't pollute our namespace end math.randomseed(os.time()) -os.setlocale("C", "numeric") minetest = core -- Load other files @@ -47,7 +46,6 @@ elseif INIT == "mainmenu" then elseif INIT == "async" then dofile(asyncpath .. "init.lua") elseif INIT == "client" then - os.setlocale = nil dofile(clientpath .. "init.lua") else error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index d93dd727..0d48d944 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -249,7 +249,7 @@ end -------------------------------------------------------------------------------- function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transparency) - local textlines = core.wrap_text(text, textlen) + local textlines = core.wrap_text(text, textlen, true) local retval = "textlist[" .. xpos .. "," .. ypos .. ";" .. width .. "," .. height .. ";" .. tl_name .. ";" diff --git a/builtin/mainmenu/tab_mods.lua b/builtin/mainmenu/tab_mods.lua index ae6a359c..1be6bd79 100644 --- a/builtin/mainmenu/tab_mods.lua +++ b/builtin/mainmenu/tab_mods.lua @@ -75,7 +75,7 @@ local function get_formspec(tabview, name, tabdata) if error == nil then local descriptiontext = descriptionfile:read("*all") - descriptionlines = core.wrap_text(descriptiontext, 42) + descriptionlines = core.wrap_text(descriptiontext, 42, true) descriptionfile:close() else descriptionlines = {} diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index c755d685..c23cbe4a 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -124,7 +124,7 @@ end local function formspec(tabview, name, tabdata) local tab_string = - "box[0,0;3.5,4.5;#999999]" .. + "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") .. ";" @@ -135,34 +135,34 @@ local function formspec(tabview, name, tabdata) .. 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.3;dd_node_highlighting;" .. dd_options.node_highlighting[1] .. ";" + "dropdown[0.25,2.8;3.5;dd_node_highlighting;" .. dd_options.node_highlighting[1] .. ";" .. getSettingIndex.NodeHighlighting() .. "]" .. - "dropdown[0.25,3.6;3.3;dd_leaves_style;" .. dd_options.leaves[1] .. ";" + "dropdown[0.25,3.6;3.5;dd_leaves_style;" .. dd_options.leaves[1] .. ";" .. getSettingIndex.Leaves() .. "]" .. - "box[3.75,0;3.75,4.45;#999999]" .. - "label[3.85,0.1;" .. fgettext("Texturing:") .. "]" .. - "dropdown[3.85,0.55;3.85;dd_filters;" .. dd_options.filters[1] .. ";" + "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[3.85,1.35;3.85;dd_mipmap;" .. dd_options.mipmap[1] .. ";" + "dropdown[4.25,1.35;3.5;dd_mipmap;" .. dd_options.mipmap[1] .. ";" .. getSettingIndex.Mipmap() .. "]" .. - "label[3.85,2.15;" .. fgettext("Antialiasing:") .. "]" .. - "dropdown[3.85,2.6;3.85;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";" + "label[4.25,2.15;" .. fgettext("Antialiasing:") .. "]" .. + "dropdown[4.25,2.6;3.5;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";" .. getSettingIndex.Antialiasing() .. "]" .. - "label[3.85,3.45;" .. fgettext("Screen:") .. "]" .. - "checkbox[3.85,3.6;cb_autosave_screensize;" .. fgettext("Autosave screen size") .. ";" + "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[7.75,0;4,4.4;#999999]" .. - "checkbox[8,0;cb_shaders;" .. fgettext("Shaders") .. ";" + "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" or PLATFORM ~= "iOS" then tab_string = tab_string .. - "button[8,4.85;3.75,0.5;btn_change_keys;" + "button[8,4.75;4,1;btn_change_keys;" .. fgettext("Change keys") .. "]" end tab_string = tab_string .. - "button[0,4.85;3.75,0.5;btn_advanced_settings;" + "button[0,4.75;4,1;btn_advanced_settings;" .. fgettext("Advanced Settings") .. "]" @@ -175,19 +175,19 @@ local function formspec(tabview, name, tabdata) if core.settings:get_bool("enable_shaders") then tab_string = tab_string .. - "checkbox[8,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";" + "checkbox[8.25,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";" .. dump(core.settings:get_bool("enable_bumpmapping")) .. "]" .. - "checkbox[8,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" + "checkbox[8.25,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" .. dump(core.settings:get_bool("tone_mapping")) .. "]" .. - "checkbox[8,1.5;cb_generate_normalmaps;" .. fgettext("Normal Mapping") .. ";" + "checkbox[8.25,1.5;cb_generate_normalmaps;" .. fgettext("Normal Mapping") .. ";" .. dump(core.settings:get_bool("generate_normalmaps")) .. "]" .. - "checkbox[8,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" + "checkbox[8.25,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" .. dump(core.settings:get_bool("enable_parallax_occlusion")) .. "]" .. - "checkbox[8,2.5;cb_waving_water;" .. fgettext("Waving Water") .. ";" + "checkbox[8.25,2.5;cb_waving_water;" .. fgettext("Waving Water") .. ";" .. dump(core.settings:get_bool("enable_waving_water")) .. "]" .. - "checkbox[8,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" + "checkbox[8.25,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" .. - "checkbox[8,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" + "checkbox[8.25,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" .. dump(core.settings:get_bool("enable_waving_plants")) .. "]" else tab_string = tab_string .. diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua index cb8b79d0..5c23a59c 100644 --- a/builtin/profiler/instrumentation.lua +++ b/builtin/profiler/instrumentation.lua @@ -133,7 +133,7 @@ local function instrument_register(func, func_name) return func(instrument { func = callback, func_name = register_name - }), ... + }, ...) end end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 2db0581e..38c2b081 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -166,10 +166,6 @@ keymap_cmd (Command key) key / # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_cmd_local (Command key) key . -# Key for opening the chat console. -# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 -keyman_console (Console key) key KEY_F10 - # Key for toggling unlimited view range. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_rangeselect (Range select key) key KEY_KEY_R @@ -476,9 +472,11 @@ pause_fps_max (FPS in pause menu) int 20 # View distance in nodes. viewing_range (Viewing range) int 100 20 4000 -# Far view distance in nodes. -# Only for Android. -viewing_range_secondary (Initial secondary viewing range) int 150 +# 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 @@ -724,10 +722,9 @@ server_announce (Announce server) bool false # If you want to announce your ipv6 address, use serverlist_url = v6.servers.minetest.net. serverlist_url (Serverlist URL) string servers.minetest.net -# Disable escape sequences, e.g. chat coloring. -# Use this if you want to run a server with pre-0.4.14 clients and you want to disable -# the escape sequences generated by mods. -disable_escape_sequences (Disable escape sequences) bool false +# 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] diff --git a/cmake/Modules/FindLuaJIT.cmake b/cmake/Modules/FindLuaJIT.cmake index e4335d83..cd6e7bdd 100644 --- a/cmake/Modules/FindLuaJIT.cmake +++ b/cmake/Modules/FindLuaJIT.cmake @@ -9,7 +9,7 @@ FIND_PATH(LUA_INCLUDE_DIR luajit.h HINTS $ENV{LUA_DIR} - PATH_SUFFIXES include/luajit-2.0 include/luajit-5_1-2.0 include + 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 diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index b3e494cc..8ef3bfb2 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -628,6 +628,9 @@ Minetest namespace reference 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(...)` @@ -1117,15 +1120,15 @@ The following functions provide escape sequences: `minetest.get_color_escape_sequence(color) .. message .. minetest.get_color_escape_sequence("#ffffff")` -* `color.get_background_escape_sequence(color)` +* `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. -* `color.strip_foreground_colors(str)` +* `minetest.strip_foreground_colors(str)` * Removes foreground colors added by `get_color_escape_sequence`. -* `color.strip_background_colors(str)` +* `minetest.strip_background_colors(str)` * Removes background colors added by `get_background_escape_sequence`. -* `color.strip_colors(str)` +* `minetest.strip_colors(str)` * Removes all color escape sequences. `ColorString` diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 66b8a019..8a95880b 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1,4 +1,4 @@ -Minetest Lua Modding API Reference 0.4.16 +Minetest Lua Modding API Reference 0.4.17 ========================================= * More information at * Developer Wiki: @@ -211,7 +211,8 @@ when registering it. The `:` prefix can also be used for maintaining backwards compatibility. -### Aliases +Aliases +------- Aliases can be added by using `minetest.register_alias(name, convert_to)` or `minetest.register_alias_force(name, convert_to)`. @@ -232,6 +233,75 @@ you have an item called `epiclylongmodname:stuff`, you could do 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 @@ -531,9 +601,26 @@ 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, you -currently must manually convert between it and the node's `param2` with -custom `on_place` and `on_dig` callbacks. +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 @@ -578,7 +665,7 @@ Example (colored grass block): description = "Dirt with Grass", -- Regular tiles, as usual -- The dirt tile disables palette coloring - tiles = {{name = "default_grass.png"}, + 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 @@ -792,6 +879,11 @@ node definition: 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 @@ -2060,15 +2152,15 @@ The following functions provide escape sequences: `minetest.get_color_escape_sequence(color) .. message .. minetest.get_color_escape_sequence("#ffffff")` -* `color.get_background_escape_sequence(color)` +* `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. -* `color.strip_foreground_colors(str)` +* `minetest.strip_foreground_colors(str)` * Removes foreground colors added by `get_color_escape_sequence`. -* `color.strip_background_colors(str)` +* `minetest.strip_background_colors(str)` * Removes background colors added by `get_background_escape_sequence`. -* `color.strip_colors(str)` +* `minetest.strip_colors(str)` * Removes all color escape sequences. Spatial Vectors @@ -2111,9 +2203,11 @@ Helper functions * 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 +* `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. @@ -2181,7 +2275,7 @@ Helper functions max_jitter = 0.5, -- maximum packet time jitter avg_jitter = 0.03, -- average packet time jitter connection_uptime = 200, -- seconds since client connected - prot_vers = 31, -- protocol version used by client + 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 @@ -2199,6 +2293,10 @@ Helper functions * 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" @@ -2210,6 +2308,9 @@ Helper functions 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(...)` @@ -2229,6 +2330,8 @@ Call these functions only at load time! * `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. @@ -2263,6 +2366,7 @@ Call these functions only at load time! * `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))` @@ -2356,8 +2460,9 @@ Call these functions only at load time! * `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(handler)` - * See `minetest.builtin_auth_handler` in `builtin.lua` for reference +* `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 @@ -2366,37 +2471,44 @@ Call these functions only at load time! parses it as a position (in the format `(1,2,3)`). Returns a position or nil. ### Authentication -* `minetest.notify_authentication_modified(name)` - * Should be called by the authentication handler if privileges changes. - * To report everybody, set `name=nil`. -* `minetest.check_password_entry(name, entry, password)` - * Returns true if the "db entry" for a player with name matches given - * password, false otherwise. - * The "db entry" is the usually player-individual value that is derived - * from the player's chosen password and stored on the server in order to allow - * authentication whenever the player desires to log in. - * Only use this function for making it possible to log in via the password from - * via protocols like IRC, other uses for inside the game 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.string_to_privs(str)`: returns `{priv1=true,...}` * `minetest.privs_to_string(privs)`: returns `"priv1,priv2,..."` * Convert between two privilege representations -* `minetest.set_player_password(name, password_hash)` -* `minetest.set_player_privs(name, {priv1=true,...})` * `minetest.get_player_privs(name) -> {priv1=true,...}` -* `minetest.auth_reload()` * `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.get_player_ip(name)`: returns an IP address string + * `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. @@ -2462,12 +2574,15 @@ and `minetest.auth_reload` call the authetification handler. * `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(minp, maxp, nodenames)`: returns a list of positions - * returns as second value a table with the count of the individual nodes found +* `minetest.find_nodes_in_area(pos1, pos2, nodenames)`: returns a list of positions * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` -* `minetest.find_nodes_in_area_under_air(minp, maxp, nodenames)`: returns a list of positions - * returned positions are nodes with a node air above + * 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`) @@ -2676,6 +2791,13 @@ and `minetest.auth_reload` call the authetification handler. * 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. @@ -2711,9 +2833,9 @@ and `minetest.auth_reload` call the authetification handler. * Example query for `"default:gold_ingot"` will return table: { - [1]={type = "cooking", width = 3, output = "default:gold_ingot", + [1]={method = "cooking", width = 3, output = "default:gold_ingot", items = {1 = "default:gold_lump"}}, - [2]={type = "normal", width = 1, output = "default:gold_ingot 9", + [2]={method = "normal", width = 1, output = "default:gold_ingot 9", items = {1 = "default:goldblock"}} } * `minetest.handle_node_drops(pos, drops, digger)` @@ -2735,9 +2857,11 @@ and `minetest.auth_reload` call the authetification handler. ### Defaults for the `on_*` item definition functions These functions return the leftover itemstack. -* `minetest.item_place_node(itemstack, placer, pointed_thing, param2)` +* `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 @@ -2893,6 +3017,7 @@ These functions return the leftover itemstack. ### 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 @@ -2960,6 +3085,7 @@ These functions return the leftover itemstack. * 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 @@ -2976,25 +3102,29 @@ These functions return the leftover itemstack. * `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)` +* `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). Stacks are handled normally if the `infinitestacks` - field is false or omitted (else, the itemstack is not changed). `orient_flags` - is an 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. + 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. + * `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, and checks for "sneak" to set the `invert_wall` - parameter. + * 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`. @@ -3150,7 +3280,7 @@ This is basically a reference to a C++ `ServerActiveObject` * `set_attach(parent, bone, position, rotation)` * `bone`: string * `position`: `{x=num, y=num, z=num}` (relative) - * `rotation`: `{x=num, y=num, z=num}` + * `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)` @@ -3218,7 +3348,7 @@ This is basically a reference to a C++ `ServerActiveObject` * `11`: bubbles bar is not shown * `set_attribute(attribute, value)`: * Sets an extra attribute with value on player. - * `value` must be a string. + * `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. @@ -3322,8 +3452,9 @@ An `InvRef` is a reference to an inventory. * `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)`: returns `true` if the stack of items - can be fully taken from 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 @@ -4212,6 +4343,7 @@ Definition tables { 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 }, }, }, @@ -4242,6 +4374,7 @@ Definition tables ^ 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 @@ -4257,9 +4390,11 @@ Definition tables ^ By default: Calls minetest.register_on_punchnode callbacks ]] on_rightclick = func(pos, node, clicker, itemstack, pointed_thing), --[[ ^ default: nil - ^ if defined, itemstack will hold clicker's wielded item + ^ itemstack will hold clicker's wielded item ^ Shall return the leftover itemstack - ^ Note: pointed_thing can be nil, if a mod calls this function ]] + ^ 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 @@ -4673,4 +4808,26 @@ The Biome API is still in an experimental phase and subject to change. code = 200, -- ^ HTTP status code data = "response" - } \ No newline at end of file + } + +### Authentication handler definition + + { + get_auth = func(name), + -- ^ Get authentication data for existing player `name` (`nil` if player doesn't exist) + -- ^ returns following structure `{password=, privileges=, last_login=}` + 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/menu_lua_api.txt b/doc/menu_lua_api.txt index 074bc962..eb0d2ed6 100644 --- a/doc/menu_lua_api.txt +++ b/doc/menu_lua_api.txt @@ -1,4 +1,4 @@ -Minetest Lua Mainmenu API Reference 0.4.16 +Minetest Lua Mainmenu API Reference 0.4.17 ======================================== Introduction diff --git a/multicraft.conf.example b/multicraft.conf.example index ed75bca0..e7d4c383 100644 --- a/multicraft.conf.example +++ b/multicraft.conf.example @@ -161,11 +161,6 @@ # type: key # keymap_cmd_local = . -# Key for opening the chat console. -# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 -# type: key -# keyman_console = KEY_F10 - # Key for toggling unlimited view range. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 # type: key @@ -344,8 +339,8 @@ # serverlist_file = favoriteservers.txt # Maximum size of the out chat queue. 0 to disable queueing and -1 to make the queue size unlimited -# type: int min: -1 -max_out_chat_queue_size = 20 +# type: int +# max_out_chat_queue_size = 20 ## Graphics @@ -555,7 +550,7 @@ max_out_chat_queue_size = 20 # type: int # screenH = 600 -# Save the window size automatically when modified. +# Save window size automatically when modified. # type: bool # autosave_screensize = true @@ -866,11 +861,10 @@ max_out_chat_queue_size = 20 # type: string # serverlist_url = servers.minetest.net -# Disable escape sequences, e.g. chat coloring. -# Use this if you want to run a server with pre-0.4.14 clients and you want to disable -# the escape sequences generated by mods. +# Remove color codes from incoming chat messages +# Use this to stop players from being able to use color in their messages # type: bool -# disable_escape_sequences = false +# strip_color_codes = false ## Network @@ -1845,3 +1839,4 @@ max_out_chat_queue_size = 20 # Print the engine's profiling data in regular intervals (in seconds). 0 = disable. Useful for developers. # type: int # profiler_print_interval = 0 + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b71887a5..f57b2089 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -268,7 +268,7 @@ if(WIN32) else() # Probably MinGW = GCC set(PLATFORM_LIBS "") endif() - set(PLATFORM_LIBS ws2_32.lib shlwapi.lib ${PLATFORM_LIBS}) + set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib ${PLATFORM_LIBS}) # Zlib stuff set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/../../zlib/zlib-1.2.5" @@ -325,10 +325,12 @@ else() endif(HAVE_LIBRT) endif(APPLE) + if(NOT APPLE) # This way Xxf86vm is found on OpenBSD too - find_library(XXF86VM_LIBRARY Xxf86vm) - mark_as_advanced(XXF86VM_LIBRARY) - set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY}) + find_library(XXF86VM_LIBRARY Xxf86vm) + mark_as_advanced(XXF86VM_LIBRARY) + set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY}) + endif(NOT APPLE) # Prefer local iconv if installed find_library(ICONV_LIBRARY iconv) diff --git a/src/camera.cpp b/src/camera.cpp index 3d06453a..7420e31f 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -388,8 +388,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, // *100.0 helps in large map coordinates m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction); - // update the camera position in front-view mode to render blocks behind player - if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) + // update the camera position in third-person mode to render blocks behind player + // and correctly apply liquid post FX. + if (m_camera_mode != CAMERA_MODE_FIRST) m_camera_position = my_cp; // Get FOV @@ -489,7 +490,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, void Camera::updateViewingRange() { f32 viewing_range = g_settings->getFloat("viewing_range"); + f32 near_plane = g_settings->getFloat("near_plane"); m_draw_control.wanted_range = viewing_range; + m_cameranode->setNearValue(rangelim(near_plane, 0.0f, 0.5f) * BS); if (m_draw_control.range_all) { m_cameranode->setFarValue(100000.0); return; diff --git a/src/cavegen.cpp b/src/cavegen.cpp index 5c2a5c9e..c6486e80 100644 --- a/src/cavegen.cpp +++ b/src/cavegen.cpp @@ -332,7 +332,7 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, route_y_min = 0; // Allow half a diameter + 7 over stone surface - route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7; + route_y_max = -of.Y + max_stone_height + max_tunnel_diameter / 2 + 7; // Limit maximum to area route_y_max = rangelim(route_y_max, 0, ar.Y - 1); diff --git a/src/cavegen.h b/src/cavegen.h index f0b2dab9..09512b1a 100644 --- a/src/cavegen.h +++ b/src/cavegen.h @@ -134,7 +134,6 @@ public: bool large_cave_is_flat; bool flooded; - s16 max_stone_y; v3s16 node_min; v3s16 node_max; diff --git a/src/cguittfont/irrUString.h b/src/cguittfont/irrUString.h index eb7abe5a..5b10e236 100644 --- a/src/cguittfont/irrUString.h +++ b/src/cguittfont/irrUString.h @@ -31,7 +31,7 @@ #ifndef __IRR_USTRING_H_INCLUDED__ #define __IRR_USTRING_H_INCLUDED__ -#if (__cplusplus > 199711L) || (_MSC_VER >= 1600) || defined(__GXX_EXPERIMENTAL_CXX0X__) +#if (__cplusplus > 199711L) || (defined(_MSC_VER) && _MSC_VER >= 1600) || defined(__GXX_EXPERIMENTAL_CXX0X__) # define USTRING_CPP0X # if defined(__GXX_EXPERIMENTAL_CXX0X__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))) # define USTRING_CPP0X_NEWLITERALS diff --git a/src/client.cpp b/src/client.cpp index 27840192..724f4dca 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -73,7 +73,6 @@ Client::Client( m_connection_reinit_timer(0.1), m_avg_rtt_timer(0.0), m_playerpos_send_timer(0.0), - m_ignore_damage_timer(0.0), m_tsrc(tsrc), m_shsrc(shsrc), m_itemdef(itemdef), @@ -239,6 +238,8 @@ Client::~Client() m_shutdown = true; m_con.Disconnect(); + deleteAuthData(); + m_mesh_update_thread.stop(); m_mesh_update_thread.wait(); while (!m_mesh_update_thread.m_queue_out.empty()) { @@ -266,6 +267,7 @@ Client::~Client() } delete m_minimap; + delete m_media_downloader; } void Client::connect(Address address, bool is_local_server) @@ -283,14 +285,9 @@ void Client::step(float dtime) DSTACK(FUNCTION_NAME); // Limit a bit - if(dtime > 2.0) + if (dtime > 2.0) dtime = 2.0; - if(m_ignore_damage_timer > dtime) - m_ignore_damage_timer -= dtime; - else - m_ignore_damage_timer = 0.0; - m_animation_time += dtime; if(m_animation_time > 60.0) m_animation_time -= 60.0; @@ -437,18 +434,16 @@ void Client::step(float dtime) ClientEnvEvent envEvent = m_env.getClientEnvEvent(); if (envEvent.type == CEE_PLAYER_DAMAGE) { - if (m_ignore_damage_timer <= 0) { - u8 damage = envEvent.player_damage.amount; + u8 damage = envEvent.player_damage.amount; - if (envEvent.player_damage.send_to_server) - sendDamage(damage); + if (envEvent.player_damage.send_to_server) + sendDamage(damage); - // Add to ClientEvent queue - ClientEvent event; - event.type = CE_PLAYER_DAMAGE; - event.player_damage.amount = damage; - m_client_event_queue.push(event); - } + // Add to ClientEvent queue + ClientEvent event; + event.type = CE_PLAYER_DAMAGE; + event.player_damage.amount = damage; + m_client_event_queue.push(event); } // Protocol v29 or greater obsoleted this event else if (envEvent.type == CEE_PLAYER_BREATH && m_proto_ver < 29) { diff --git a/src/client.h b/src/client.h index 4ffb90a0..b063c168 100644 --- a/src/client.h +++ b/src/client.h @@ -574,7 +574,6 @@ private: float m_connection_reinit_timer; float m_avg_rtt_timer; float m_playerpos_send_timer; - float m_ignore_damage_timer; // Used after server moves player IntervalLimiter m_map_timer_and_unload_interval; IWritableTextureSource *m_tsrc; diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h index 1507589d..f1256b00 100644 --- a/src/client/joystick_controller.h +++ b/src/client/joystick_controller.h @@ -60,6 +60,8 @@ struct JoystickButtonCmb : public JoystickCombination { this->key = key; } + virtual ~JoystickButtonCmb() {} + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; u32 filter_mask; @@ -77,6 +79,8 @@ struct JoystickAxisCmb : public JoystickCombination { this->key = key; } + virtual ~JoystickAxisCmb() {} + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; u16 axis_to_compare; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index b1bc4518..40280635 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -1139,13 +1139,14 @@ video::IImage * Align2Npot2(video::IImage * image, core::dimension2d dim = image->getDimension(); - std::string extensions = (char*) glGetString(GL_EXTENSIONS); - // Only GLES2 is trusted to correctly report npot support - if (get_GL_major_version() > 1 && - extensions.find("GL_OES_texture_npot") != std::string::npos) { + // Note: we cache the boolean result. GL context will never change on Android. + static const bool hasNPotSupport = get_GL_major_version() > 1 && + glGetString(GL_EXTENSIONS) && + strstr(glGetString(GL_EXTENSIONS), "GL_OES_texture_npot"); + + if (hasNPotSupport) return image; - } unsigned int height = npot2(dim.Height); unsigned int width = npot2(dim.Width); @@ -1811,7 +1812,8 @@ bool TextureSource::generateImagePart(std::string part_of_name, * mix high- and low-res textures, or for mods with least-common-denominator * textures that don't have the resources to offer high-res alternatives. */ - s32 scaleto = g_settings->getS32("texture_min_size"); + const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter; + const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1; if (scaleto > 1) { const core::dimension2d dim = baseimg->getDimension(); diff --git a/src/client/tile.h b/src/client/tile.h index b4b30712..4309331b 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -159,7 +159,8 @@ enum MaterialType{ TILE_MATERIAL_LIQUID_TRANSPARENT, TILE_MATERIAL_LIQUID_OPAQUE, TILE_MATERIAL_WAVING_LEAVES, - TILE_MATERIAL_WAVING_PLANTS + TILE_MATERIAL_WAVING_PLANTS, + TILE_MATERIAL_OPAQUE }; // Material flags @@ -243,18 +244,20 @@ struct TileLayer void applyMaterialOptions(video::SMaterial &material) const { switch (material_type) { + case TILE_MATERIAL_OPAQUE: + case TILE_MATERIAL_LIQUID_OPAQUE: + material.MaterialType = video::EMT_SOLID; + break; case TILE_MATERIAL_BASIC: case TILE_MATERIAL_WAVING_LEAVES: case TILE_MATERIAL_WAVING_PLANTS: + material.MaterialTypeParam = 0.5; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; break; case TILE_MATERIAL_ALPHA: case TILE_MATERIAL_LIQUID_TRANSPARENT: material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; break; - case TILE_MATERIAL_LIQUID_OPAQUE: - material.MaterialType = video::EMT_SOLID; - break; } material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) ? true : false; diff --git a/src/clientiface.cpp b/src/clientiface.cpp index 789038f4..80a57cd7 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -633,6 +633,16 @@ std::vector ClientInterface::getClientIDs(ClientState min_state) return reply; } +/** + * Verify if user limit was reached. + * User limit count all clients from HelloSent state (MT protocol user) to Active state + * @return true if user limit was reached + */ +bool ClientInterface::isUserLimitReached() +{ + return getClientIDs(CS_HelloSent).size() >= g_settings->getU16("max_users"); +} + void ClientInterface::step(float dtime) { m_print_info_timer += dtime; diff --git a/src/clientiface.h b/src/clientiface.h index 237c8baf..717abe0a 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -449,6 +449,9 @@ public: /* get list of active client id's */ std::vector getClientIDs(ClientState min_state=CS_Active); + /* verify is server user limit was reached */ + bool isUserLimitReached(); + /* get list of client player names */ const std::vector &getPlayerNames() const { return m_clients_names; } @@ -493,7 +496,6 @@ public: } static std::string state2Name(ClientState state); - protected: //TODO find way to avoid this functions void lock() { m_clients_mutex.lock(); } diff --git a/src/clientmap.cpp b/src/clientmap.cpp index 08a8f790..b41f5646 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -294,49 +294,46 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) struct MeshBufList { - /*! - * Specifies in which layer the list is. - * All lists which are in a lower layer are rendered before this list. - */ - u8 layer; video::SMaterial m; std::vector bufs; }; struct MeshBufListList { - std::vector lists; + /*! + * Stores the mesh buffers of the world. + * The array index is the material's layer. + * The vector part groups vertices by material. + */ + std::vector lists[MAX_TILE_LAYERS]; void clear() { - lists.clear(); + for (int l = 0; l < MAX_TILE_LAYERS; l++) + lists[l].clear(); } void add(scene::IMeshBuffer *buf, u8 layer) { + // Append to the correct layer + std::vector &list = lists[layer]; const video::SMaterial &m = buf->getMaterial(); - for(std::vector::iterator i = lists.begin(); - i != lists.end(); ++i){ - MeshBufList &l = *i; - + for (std::vector::iterator it = list.begin(); it != list.end(); + ++it) { // comparing a full material is quite expensive so we don't do it if // not even first texture is equal - if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture) + if ((*it).m.TextureLayer[0].Texture != m.TextureLayer[0].Texture) continue; - if(l.layer != layer) - continue; - - if (l.m == m) { - l.bufs.push_back(buf); + if ((*it).m == m) { + (*it).bufs.push_back(buf); return; } } MeshBufList l; - l.layer = layer; l.m = m; l.bufs.push_back(buf); - lists.push_back(l); + list.push_back(l); } }; @@ -364,7 +361,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) Measuring time is very useful for long delays when the machine is swapping a lot. */ - int time1 = time(0); + time_t time1 = time(0); /* Get animation parameters @@ -480,35 +477,34 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) } } - std::vector &lists = drawbufs.lists; + // Render all layers in order + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + std::vector &lists = drawbufs.lists[layer]; - int timecheck_counter = 0; - for (std::vector::iterator i = lists.begin(); - i != lists.end(); ++i) { - timecheck_counter++; - if (timecheck_counter > 50) { - timecheck_counter = 0; - int time2 = time(0); - if (time2 > time1 + 4) { - infostream << "ClientMap::renderMap(): " - "Rendering takes ages, returning." - << std::endl; - return; + int timecheck_counter = 0; + for (std::vector::iterator it = lists.begin(); it != lists.end(); + ++it) { + timecheck_counter++; + if (timecheck_counter > 50) { + timecheck_counter = 0; + time_t time2 = time(0); + if (time2 > time1 + 4) { + infostream << "ClientMap::renderMap(): " + "Rendering takes ages, returning." + << std::endl; + return; + } + } + + driver->setMaterial((*it).m); + + for (std::vector::iterator it2 = (*it).bufs.begin(); + it2 != (*it).bufs.end(); ++it2) { + driver->drawMeshBuffer(*it2); + vertex_count += (*it2)->getVertexCount(); + meshbuffer_count++; } } - - MeshBufList &list = *i; - - driver->setMaterial(list.m); - - for (std::vector::iterator j = list.bufs.begin(); - j != list.bufs.end(); ++j) { - scene::IMeshBuffer *buf = *j; - driver->drawMeshBuffer(buf); - vertex_count += buf->getVertexCount(); - meshbuffer_count++; - } - } } // ScopeProfiler diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index 4b731020..9a57d3de 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -8,6 +8,7 @@ #define VERSION_MAJOR @VERSION_MAJOR@ #define VERSION_MINOR @VERSION_MINOR@ #define VERSION_PATCH @VERSION_PATCH@ +#define VERSION_TWEAK @VERSION_TWEAK@ #define VERSION_EXTRA "@VERSION_EXTRA@" #define VERSION_STRING "@VERSION_STRING@" #define PRODUCT_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@" diff --git a/src/collision.cpp b/src/collision.cpp index 431e5f4b..f20bd222 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -258,27 +258,32 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, //TimeTaker tt2("collisionMoveSimple collect boxes"); ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); - v3s16 oldpos_i = floatToInt(*pos_f, BS); - v3s16 newpos_i = floatToInt(*pos_f + *speed_f * dtime, BS); - s16 min_x = MYMIN(oldpos_i.X, newpos_i.X) + (box_0.MinEdge.X / BS) - 1; - s16 min_y = MYMIN(oldpos_i.Y, newpos_i.Y) + (box_0.MinEdge.Y / BS) - 1; - s16 min_z = MYMIN(oldpos_i.Z, newpos_i.Z) + (box_0.MinEdge.Z / BS) - 1; - s16 max_x = MYMAX(oldpos_i.X, newpos_i.X) + (box_0.MaxEdge.X / BS) + 1; - s16 max_y = MYMAX(oldpos_i.Y, newpos_i.Y) + (box_0.MaxEdge.Y / BS) + 1; - s16 max_z = MYMAX(oldpos_i.Z, newpos_i.Z) + (box_0.MaxEdge.Z / BS) + 1; + v3f newpos_f = *pos_f + *speed_f * dtime; + v3f minpos_f( + MYMIN(pos_f->X, newpos_f.X), + MYMIN(pos_f->Y, newpos_f.Y) + 0.01 * BS, // bias rounding, player often at +/-n.5 + MYMIN(pos_f->Z, newpos_f.Z) + ); + v3f maxpos_f( + MYMAX(pos_f->X, newpos_f.X), + MYMAX(pos_f->Y, newpos_f.Y), + MYMAX(pos_f->Z, newpos_f.Z) + ); + v3s16 min = floatToInt(minpos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1); + v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1); bool any_position_valid = false; - for(s16 x = min_x; x <= max_x; x++) - for(s16 y = min_y; y <= max_y; y++) - for(s16 z = min_z; z <= max_z; z++) + for(s16 x = min.X; x <= max.X; x++) + for(s16 y = min.Y; y <= max.Y; y++) + for(s16 z = min.Z; z <= max.Z; z++) { v3s16 p(x,y,z); bool is_position_valid; MapNode n = map->getNodeNoEx(p, &is_position_valid); - if (is_position_valid) { + if (is_position_valid && n.getContent() != CONTENT_IGNORE) { // Object collides into walkable nodes any_position_valid = true; @@ -328,7 +333,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, false, n_bouncy_value, p, box)); } } else { - // Collide with unloaded nodes + // Collide with unloaded nodes (position invalid) and loaded + // CONTENT_IGNORE nodes (position valid) aabb3f box = getNodeBox(p, BS); cinfo.push_back(NearbyCollisionInfo(true, false, 0, p, box)); } @@ -336,6 +342,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, // Do not move if world has not loaded yet, since custom node boxes // are not available for collision detection. + // This also intentionally occurs in the case of the object being positioned + // solely on loaded CONTENT_IGNORE nodes, no matter where they come from. if (!any_position_valid) { *speed_f = v3f(0, 0, 0); return result; diff --git a/src/content_cao.cpp b/src/content_cao.cpp index ab3ac2a4..892ce471 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -1183,16 +1183,17 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) float moved = lastpos.getDistanceFrom(pos_translator.vect_show); m_step_distance_counter += moved; - if(m_step_distance_counter > 1.5*BS) - { - m_step_distance_counter = 0; - if(!m_is_local_player && m_prop.makes_footstep_sound) - { + if (m_step_distance_counter > 1.5f * BS) { + m_step_distance_counter = 0.0f; + if (!m_is_local_player && m_prop.makes_footstep_sound) { INodeDefManager *ndef = m_client->ndef(); - v3s16 p = floatToInt(getPosition() + v3f(0, - (m_prop.collisionbox.MinEdge.Y-0.5)*BS, 0), BS); + v3s16 p = floatToInt(getPosition() + + v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS); MapNode n = m_env->getMap().getNodeNoEx(p); SimpleSoundSpec spec = ndef->get(n).sound_footstep; + // Reduce footstep gain, as non-local-player footsteps are + // somehow louder. + spec.gain *= 0.6f; m_client->sound()->playSoundAt(spec, false, getPosition()); } } @@ -1353,6 +1354,13 @@ void GenericCAO::updateTextures(std::string mod) material.setFlag(video::EMF_LIGHTING, false); material.setFlag(video::EMF_BILINEAR_FILTER, false); + // don't filter low-res textures, makes them look blurry + // player models have a res of 64 + const core::dimension2d &size = texture->getOriginalSize(); + const u32 res = std::min(size.Height, size.Width); + use_trilinear_filter &= res > 64; + use_bilinear_filter &= res > 64; + m_animated_meshnode->getMaterial(i) .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); m_animated_meshnode->getMaterial(i) diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 52587f3b..e0d60db4 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // Corresponding offsets are listed in g_27dirs #define FRAMED_NEIGHBOR_COUNT 18 -static constexpr v3s16 light_dirs[8] = { +static const v3s16 light_dirs[8] = { v3s16(-1, -1, -1), v3s16(-1, -1, 1), v3s16(-1, 1, -1), diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 0d28620e..8ed3093c 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -59,7 +59,7 @@ public: m_age += dtime; if(m_age > 10) { - m_removed = true; + m_pending_removal = true; return; } @@ -406,20 +406,6 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_env->getScriptIface()->luaentity_Step(m_id, dtime); } - // Remove LuaEntity beyond terrain edges - { - ServerMap *map = dynamic_cast(&m_env->getMap()); - assert(map); - if (!m_pending_deactivation && - map->saoPositionOverLimit(m_base_position)) { - infostream << "Remove SAO " << m_id << "(" << m_init_name - << "), outside of limits" << std::endl; - m_pending_deactivation = true; - m_removed = true; - return; - } - } - if(send_recommended == false) return; @@ -555,9 +541,9 @@ int LuaEntitySAO::punch(v3f dir, ServerActiveObject *puncher, float time_from_last_punch) { - if (!m_registered){ + if (!m_registered) { // Delete unknown LuaEntities when punched - m_removed = true; + m_pending_removal = true; return 0; } @@ -601,7 +587,7 @@ int LuaEntitySAO::punch(v3f dir, } if (getHP() == 0) - m_removed = true; + m_pending_removal = true; @@ -1361,11 +1347,10 @@ void PlayerSAO::setWieldIndex(int i) } } -// Erase the peer id and make the object for removal void PlayerSAO::disconnected() { m_peer_id = 0; - m_removed = true; + m_pending_removal = true; } void PlayerSAO::unlinkPlayerSessionAndSave() @@ -1402,26 +1387,38 @@ bool PlayerSAO::checkMovementCheat() too, and much more lightweight. */ - float player_max_speed = 0; + float player_max_walk = 0; // horizontal movement + float player_max_jump = 0; // vertical upwards movement - if (m_privs.count("fast") != 0) { - // Fast speed - player_max_speed = m_player->movement_speed_fast * m_physics_override_speed; - } else { - // Normal speed - player_max_speed = m_player->movement_speed_walk * m_physics_override_speed; - } - // Tolerance. The lag pool does this a bit. - //player_max_speed *= 2.5; + if (m_privs.count("fast") != 0) + player_max_walk = m_player->movement_speed_fast; // Fast speed + else + player_max_walk = m_player->movement_speed_walk; // Normal speed + player_max_walk *= m_physics_override_speed; + player_max_jump = m_player->movement_speed_jump * m_physics_override_jump; + // FIXME: Bouncy nodes cause practically unbound increase in Y speed, + // until this can be verified correctly, tolerate higher jumping speeds + player_max_jump *= 2.0; + + // Don't divide by zero! + if (player_max_walk < 0.0001f) + player_max_walk = 0.0001f; + if (player_max_jump < 0.0001f) + player_max_jump = 0.0001f; v3f diff = (m_base_position - m_last_good_position); float d_vert = diff.Y; diff.Y = 0; float d_horiz = diff.getLength(); - float required_time = d_horiz / player_max_speed; + float required_time = d_horiz / player_max_walk; - if (d_vert > 0 && d_vert / player_max_speed > required_time) - required_time = d_vert / player_max_speed; // Moving upwards + // FIXME: Checking downwards movement is not easily possible currently, + // the server could calculate speed differences to examine the gravity + if (d_vert > 0) { + // In certain cases (water, ladders) walking speed is applied vertically + float s = MYMAX(player_max_jump, player_max_walk); + required_time = MYMAX(required_time, d_vert / s); + } if (m_move_pool.grab(required_time)) { m_last_good_position = m_base_position; diff --git a/src/craftdef.cpp b/src/craftdef.cpp index e79dc327..27f00f3e 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -923,8 +923,19 @@ public: << " against " << def->dump() << std::endl;*/ if (def->check(input, gamedef)) { + // Check if the crafted node/item exists + CraftOutput out = def->getOutput(input, gamedef); + ItemStack is; + is.deSerialize(out.item, gamedef->idef()); + if (!is.isKnown(gamedef->idef())) { + infostream << "trying to craft non-existent " + << out.item << ", ignoring recipe" << std::endl; + continue; + } + // Get output, then decrement input (if requested) - output = def->getOutput(input, gamedef); + output = out; + if (decrementInput) def->decrementInput(input, output_replacement, gamedef); /*errorstream << "Check RETURNS TRUE" << std::endl;*/ diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f56c0793..e6dd93c3 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -129,8 +129,9 @@ void set_default_settings(Settings *settings) settings->setDefault("fps_max", "60"); settings->setDefault("pause_fps_max", "10"); settings->setDefault("viewing_range", "100"); - settings->setDefault("screen_w", "800"); - settings->setDefault("screen_h", "600"); + settings->setDefault("near_plane", "0.1"); + settings->setDefault("screenW", "800"); + settings->setDefault("screenH", "600"); settings->setDefault("autosave_screensize", "true"); settings->setDefault("fullscreen", "false"); settings->setDefault("fullscreen_bpp", "24"); @@ -259,6 +260,7 @@ void set_default_settings(Settings *settings) // Server settings->setDefault("disable_escape_sequences", "false"); + settings->setDefault("strip_color_codes", "false"); // Network settings->setDefault("enable_ipv6", "true"); @@ -371,8 +373,8 @@ void set_default_settings(Settings *settings) // Mobile Platform #if defined(__ANDROID__) || defined(__IOS__) - settings->setDefault("screen_w", "0"); - settings->setDefault("screen_h", "0"); + settings->setDefault("screenW", "0"); + settings->setDefault("screenH", "0"); settings->setDefault("fps_max", "35"); settings->setDefault("enable_shaders", "false"); settings->setDefault("fullscreen", "true"); diff --git a/src/dungeongen.cpp b/src/dungeongen.cpp index 05e1b586..c7f11834 100644 --- a/src/dungeongen.cpp +++ b/src/dungeongen.cpp @@ -431,8 +431,10 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_wall), 0); - makeHole(p); - makeHole(p - dir); + makeFill(p, dp.holesize, VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE); + makeFill(p - dir, dp.holesize, VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE); // TODO: fix stairs code so it works 100% // (quite difficult) @@ -451,16 +453,21 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) : v3s16(0, 0, 1); for (u16 st = 0; st < stair_width; st++) { - u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z); - if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) && - vm->m_data[vi].getContent() == dp.c_wall) - vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); - - vi = vm->m_area.index(ps.X, ps.Y, ps.Z); - if (vm->m_area.contains(ps) && - vm->m_data[vi].getContent() == dp.c_wall) - vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); - + if (make_stairs == -1) { + u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z); + if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) && + vm->m_data[vi].getContent() == dp.c_wall) { + vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; + vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + } + } else if (make_stairs == 1) { + u32 vi = vm->m_area.index(ps.X, ps.Y - 1, ps.Z); + if (vm->m_area.contains(ps + v3s16(0, -1, 0)) && + vm->m_data[vi].getContent() == dp.c_wall) { + vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; + vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + } + } ps += swv; } } diff --git a/src/emerge.cpp b/src/emerge.cpp index b6e1d3be..4455d741 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -146,7 +146,10 @@ EmergeManager::~EmergeManager() } delete thread; - delete m_mapgens[i]; + + // Mapgen init might not be finished if there is an error during startup. + if (m_mapgens.size() > i) + delete m_mapgens[i]; } delete biomemgr; @@ -570,6 +573,12 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, m_server->setAsyncFatalError("Lua: finishGen" + std::string(e.what())); } + /* + Clear generate notifier events + */ + Mapgen *mg = m_emerge->getCurrentMapgen(); + mg->gennotify.clearEvents(); + EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); /* diff --git a/src/filesys.cpp b/src/filesys.cpp index a80f1228..94878ec6 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -27,6 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "config.h" #include "porting.h" +#ifdef __ANDROID__ +#include "settings.h" // For g_settings +#endif namespace fs { @@ -741,4 +744,3 @@ bool Rename(const std::string &from, const std::string &to) } } // namespace fs - diff --git a/src/fontengine.cpp b/src/fontengine.cpp index c96e3b17..427c8065 100644 --- a/src/fontengine.cpp +++ b/src/fontengine.cpp @@ -341,24 +341,62 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode) font_path.c_str(), size, true, true, font_shadow, font_shadow_alpha); - if (font != NULL) { + if (font) { m_font_cache[mode][basesize] = font; return; } - // try fallback font - errorstream << "FontEngine: failed to load: " << font_path << ", trying to fall back " - "to fallback font" << std::endl; + if (font_config_prefix == "mono_") { + const std::string &mono_font_path = m_settings->getDefault("mono_font_path"); - font_path = g_settings->get(font_config_prefix + "fallback_font_path"); + if (font_path != mono_font_path) { + // try original mono font + errorstream << "FontEngine: failed to load custom mono " + "font: " << font_path << ", trying to fall back to " + "original mono font" << std::endl; - font = gui::CGUITTFont::createTTFont(m_env, - font_path.c_str(), size, true, true, font_shadow, - font_shadow_alpha); + font = gui::CGUITTFont::createTTFont(m_env, + mono_font_path.c_str(), size, true, true, + font_shadow, font_shadow_alpha); - if (font != NULL) { - m_font_cache[mode][basesize] = font; - return; + if (font) { + m_font_cache[mode][basesize] = font; + return; + } + } + } else { + // try fallback font + errorstream << "FontEngine: failed to load: " << font_path << + ", trying to fall back to fallback font" << std::endl; + + font_path = g_settings->get(font_config_prefix + "fallback_font_path"); + + font = gui::CGUITTFont::createTTFont(m_env, + font_path.c_str(), size, true, true, font_shadow, + font_shadow_alpha); + + if (font) { + m_font_cache[mode][basesize] = font; + return; + } + + const std::string &fallback_font_path = m_settings->getDefault("fallback_font_path"); + + if (font_path != fallback_font_path) { + // try original fallback font + errorstream << "FontEngine: failed to load custom fallback " + "font: " << font_path << ", trying to fall back to " + "original fallback font" << std::endl; + + font = gui::CGUITTFont::createTTFont(m_env, + fallback_font_path.c_str(), size, true, true, + font_shadow, font_shadow_alpha); + + if (font) { + m_font_cache[mode][basesize] = font; + return; + } + } } // give up @@ -367,9 +405,9 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode) #endif errorstream << "FontEngine: failed to load freetype font: " << font_path << std::endl; - errorstream << "minetest can not continue without a valid font. Please correct " - "the 'font_path' setting or install the font file in the proper " - "location" << std::endl; + errorstream << "minetest can not continue without a valid font. " + "Please correct the 'font_path' setting or install the font " + "file in the proper location" << std::endl; abort(); } #endif @@ -471,7 +509,7 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) } } - if (font != NULL) { + if (font) { font->grab(); m_font_cache[mode][basesize] = font; } diff --git a/src/game.cpp b/src/game.cpp index 0896671e..d67242c3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -185,7 +185,8 @@ struct LocalFormspecHandler : public TextDest #endif // Don't disable this part when modding is disabled, it's used in builtin - m_client->getScript()->on_formspec_input(m_formname, fields); + if (m_client && m_client->getScript()) + m_client->getScript()->on_formspec_input(m_formname, fields); } Client *m_client; @@ -789,8 +790,8 @@ public: }; -bool nodePlacementPrediction(Client &client, - const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos) +bool nodePlacementPrediction(Client &client, const ItemDefinition &playeritem_def, + const ItemStack &playeritem, v3s16 nodepos, v3s16 neighbourpos) { std::string prediction = playeritem_def.node_placement_prediction; INodeDefManager *nodedef = client.ndef(); @@ -833,11 +834,13 @@ bool nodePlacementPrediction(Client &client, return false; } + const ContentFeatures &predicted_f = nodedef->get(id); + // Predict param2 for facedir and wallmounted nodes u8 param2 = 0; - if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED || - nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) { + if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || + predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { v3s16 dir = nodepos - neighbourpos; if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { @@ -849,8 +852,8 @@ bool nodePlacementPrediction(Client &client, } } - if (nodedef->get(id).param_type_2 == CPT2_FACEDIR || - nodedef->get(id).param_type_2 == CPT2_COLORED_FACEDIR) { + if (predicted_f.param_type_2 == CPT2_FACEDIR || + predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) { v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS); if (abs(dir.X) > abs(dir.Z)) { @@ -863,7 +866,7 @@ bool nodePlacementPrediction(Client &client, assert(param2 <= 5); //Check attachment if node is in group attached_node - if (((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0) { + if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) { static v3s16 wallmounted_dirs[8] = { v3s16(0, 1, 0), v3s16(0, -1, 0), @@ -874,8 +877,8 @@ bool nodePlacementPrediction(Client &client, }; v3s16 pp; - if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED || - nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) + if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || + predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) pp = p + wallmounted_dirs[param2]; else pp = p + v3s16(0, -1, 0); @@ -884,6 +887,28 @@ bool nodePlacementPrediction(Client &client, return false; } + // Apply color + if ((predicted_f.param_type_2 == CPT2_COLOR + || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR + || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) { + const std::string &indexstr = playeritem.metadata.getString( + "palette_index", 0); + if (!indexstr.empty()) { + s32 index = mystoi(indexstr); + if (predicted_f.param_type_2 == CPT2_COLOR) { + param2 = index; + } else if (predicted_f.param_type_2 + == CPT2_COLORED_WALLMOUNTED) { + // param2 = pure palette index + other + param2 = (index & 0xf8) | (param2 & 0x07); + } else if (predicted_f.param_type_2 + == CPT2_COLORED_FACEDIR) { + // param2 = pure palette index + other + param2 = (index & 0xe0) | (param2 & 0x1f); + } + } + } + // Add node to client map MapNode n(id, 0, param2); @@ -1298,8 +1323,9 @@ protected: const core::line3d &shootline, bool liquids_pointable, bool look_for_object, const v3s16 &camera_offset); void handlePointingAtNothing(const ItemStack &playerItem); - void handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def, - const ToolCapabilities &playeritem_toolcap, f32 dtime); + void handlePointingAtNode(const PointedThing &pointed, + const ItemDefinition &playeritem_def, const ItemStack &playeritem, + const ToolCapabilities &playeritem_toolcap, f32 dtime); void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, const v3f &player_position, bool show_debug); void handleDigging(const PointedThing &pointed, const v3s16 &nodepos, @@ -2983,8 +3009,8 @@ void Game::toggleFullViewRange() }; #else static const wchar_t *msg[] = { - L"Disabled full viewing range", - L"Enabled full viewing range" + L"Normal view range", + L"Infinite view range" }; #endif @@ -3646,7 +3672,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) if (playeritem.name.empty() && hand_def.tool_capabilities != NULL) { playeritem_toolcap = *hand_def.tool_capabilities; } - handlePointingAtNode(pointed, playeritem_def, playeritem_toolcap, dtime); + handlePointingAtNode(pointed, playeritem_def, playeritem, + playeritem_toolcap, dtime); } else if (pointed.type == POINTEDTHING_OBJECT) { handlePointingAtObject(pointed, playeritem, player_position, show_debug); } else if (isLeftPressed()) { @@ -3781,8 +3808,9 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem) } -void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def, - const ToolCapabilities &playeritem_toolcap, f32 dtime) +void Game::handlePointingAtNode(const PointedThing &pointed, + const ItemDefinition &playeritem_def, const ItemStack &playeritem, + const ToolCapabilities &playeritem_toolcap, f32 dtime) { v3s16 nodepos = pointed.node_undersurface; v3s16 neighbourpos = pointed.node_abovesurface; @@ -3833,6 +3861,11 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio if (meta && meta->getString("formspec") != "" && !random_input && !isKeyDown(KeyType::SNEAK)) { + // Report right click to server + if (nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) { + client->interact(3, pointed); + } + infostream << "Launching custom inventory view" << std::endl; InventoryLocation inventoryloc; @@ -3855,7 +3888,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio // If the wielded item has node placement prediction, // make that happen bool placed = nodePlacementPrediction(*client, - playeritem_def, + playeritem_def, playeritem, nodepos, neighbourpos); if (placed) { diff --git a/src/gettext.h b/src/gettext.h index b482cec0..775cdb33 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -52,12 +52,13 @@ extern wchar_t *utf8_to_wide_c(const char *str); // The returned string is allocated using new inline const wchar_t *wgettext(const char *str) { - return utf8_to_wide_c(gettext(str)); + // We must check here that is not an empty string to avoid trying to translate it + return str[0] ? utf8_to_wide_c(gettext(str)) : utf8_to_wide_c(""); } inline std::string strgettext(const std::string &text) { - return gettext(text.c_str()); + return text.empty() ? "" : gettext(text.c_str()); } #endif diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp index 0da05985..2e399e29 100644 --- a/src/guiChatConsole.cpp +++ b/src/guiChatConsole.cpp @@ -204,8 +204,8 @@ void GUIChatConsole::draw() // scale current console height to new window size if (m_screensize.Y != 0) m_height = m_height * screensize.Y / m_screensize.Y; - m_desired_height = m_desired_height_fraction * m_screensize.Y; m_screensize = screensize; + m_desired_height = m_desired_height_fraction * m_screensize.Y; reformatConsole(); } @@ -231,6 +231,7 @@ void GUIChatConsole::reformatConsole() s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt if (cols <= 0 || rows <= 0) cols = rows = 0; + recalculateConsolePosition(); m_chat_backend->reformat(cols, rows); } diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index 68fb0aaa..ee70854a 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -86,24 +86,30 @@ MenuTextureSource::~MenuTextureSource() /******************************************************************************/ video::ITexture* MenuTextureSource::getTexture(const std::string &name, u32 *id) { - if(id) + if (id) *id = 0; - if(name.empty()) + + if (name.empty()) return NULL; + m_to_delete.insert(name); #if defined(__ANDROID__) || defined(__IOS__) - if (m_driver->findTexture(name.c_str()) != NULL) - return m_driver->findTexture(name.c_str()); - video::IImage *image = m_driver->createImageFromFile(name.c_str()); - if (image) { - image = Align2Npot2(image, m_driver); - video::ITexture* retval = m_driver->addTexture(name.c_str(), image); - image->drop(); + video::ITexture *retval = m_driver->findTexture(name.c_str()); + if (retval) return retval; - } -#endif + + video::IImage *image = m_driver->createImageFromFile(name.c_str()); + if (!image) + return NULL; + + image = Align2Npot2(image, m_driver); + retval = m_driver->addTexture(name.c_str(), image); + image->drop(); + return retval; +#else return m_driver->getTexture(name.c_str()); +#endif } /******************************************************************************/ diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index 90608dee..ca162754 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -3683,18 +3683,24 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) a->from_i = m_selected_item->i; m_invmgr->inventoryAction(a); } else if (craft_amount > 0) { - m_selected_content_guess = ItemStack(); // Clear - - // Send IACTION_CRAFT - assert(s.isValid()); - assert(inv_s); + + // if there are no items selected or the selected item + // belongs to craftresult list, proceed with crafting + if (m_selected_item == NULL || + !m_selected_item->isValid() || m_selected_item->listname == "craftresult") { + + m_selected_content_guess = ItemStack(); // Clear + + assert(inv_s); - infostream << "Handing IACTION_CRAFT to manager" << std::endl; - ICraftAction *a = new ICraftAction(); - a->count = craft_amount; - a->craft_inv = s.inventoryloc; - m_invmgr->inventoryAction(a); + // Send IACTION_CRAFT + infostream << "Handing IACTION_CRAFT to manager" << std::endl; + ICraftAction *a = new ICraftAction(); + a->count = craft_amount; + a->craft_inv = s.inventoryloc; + m_invmgr->inventoryAction(a); + } } // If m_selected_amount has been decreased to zero, deselect diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 21eb236a..f9b83d94 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -248,6 +248,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1); + curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip"); std::string bind_address = g_settings->get("bind_address"); if (!bind_address.empty()) { diff --git a/src/hud.cpp b/src/hud.cpp index d91b348a..a8bbacbd 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -409,24 +409,32 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture, p.Y -= g_settings->getU16("hud_move_upwards"); v2s32 steppos; + core::rect srchalfrect, dsthalfrect; switch (drawdir) { case HUD_DIR_RIGHT_LEFT: steppos = v2s32(-1, 0); + srchalfrect = core::rect(srcd.Width / 2, 0, srcd.Width, srcd.Height); + dsthalfrect = core::rect(dstd.Width / 2, 0, dstd.Width, dstd.Height); break; case HUD_DIR_TOP_BOTTOM: steppos = v2s32(0, 1); + srchalfrect = core::rect(0, 0, srcd.Width, srcd.Height / 2); + dsthalfrect = core::rect(0, 0, dstd.Width, dstd.Height / 2); break; case HUD_DIR_BOTTOM_TOP: steppos = v2s32(0, -1); + srchalfrect = core::rect(0, srcd.Height / 2, srcd.Width, srcd.Height); + dsthalfrect = core::rect(0, dstd.Height / 2, dstd.Width, dstd.Height); break; default: steppos = v2s32(1, 0); + srchalfrect = core::rect(0, 0, srcd.Width / 2, srcd.Height); + dsthalfrect = core::rect(0, 0, dstd.Width / 2, dstd.Height); } steppos.X *= dstd.Width; steppos.Y *= dstd.Height; - for (s32 i = 0; i < count / 2; i++) - { + for (s32 i = 0; i < count / 2; i++) { core::rect srcrect(0, 0, srcd.Width, srcd.Height); core::rect dstrect(0,0, dstd.Width, dstd.Height); @@ -435,13 +443,9 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture, p += steppos; } - if (count % 2 == 1) - { - core::rect srcrect(0, 0, srcd.Width / 2, srcd.Height); - core::rect dstrect(0,0, dstd.Width / 2, dstd.Height); - - dstrect += p; - draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true); + if (count % 2 == 1) { + dsthalfrect += p; + draw2DImageFilterScaled(driver, stat_texture, dsthalfrect, srchalfrect, NULL, colors, true); } } diff --git a/src/intlGUIEditBox.cpp b/src/intlGUIEditBox.cpp index 37687e1e..74b9f634 100644 --- a/src/intlGUIEditBox.cpp +++ b/src/intlGUIEditBox.cpp @@ -1418,14 +1418,14 @@ void intlGUIEditBox::calculateScrollPos() // todo: adjust scrollbar } - // vertical scroll position - if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos) - VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos; + if (!WordWrap && !MultiLine) + return; - else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos) - VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos; - else - VScrollPos = 0; + // vertical scroll position + if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y) + VScrollPos += CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y; // scrolling downwards + else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y) + VScrollPos += CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y; // scrolling upwards // todo: adjust scrollbar } diff --git a/src/inventory.cpp b/src/inventory.cpp index 6802f9ee..c3a306ce 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -659,7 +659,7 @@ bool InventoryList::roomForItem(const ItemStack &item_) const return false; } -bool InventoryList::containsItem(const ItemStack &item) const +bool InventoryList::containsItem(const ItemStack &item, bool match_meta) const { u32 count = item.count; if(count == 0) @@ -670,9 +670,9 @@ bool InventoryList::containsItem(const ItemStack &item) const { if(count == 0) break; - if(i->name == item.name) - { - if(i->count >= count) + if (i->name == item.name + && (!match_meta || (i->metadata == item.metadata))) { + if (i->count >= count) return true; else count -= i->count; diff --git a/src/inventory.h b/src/inventory.h index ce141b12..d7f1ee8b 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -223,9 +223,10 @@ public: // Checks whether there is room for a given item bool roomForItem(const ItemStack &item) const; - // Checks whether the given count of the given item name + // Checks whether the given count of the given item // exists in this inventory list. - bool containsItem(const ItemStack &item) const; + // If match_meta is false, only the items' names are compared. + bool containsItem(const ItemStack &item, bool match_meta) const; // Removes the given count of the given item name from // this inventory list. Walks the list in reverse order. diff --git a/src/irr_v3d.h b/src/irr_v3d.h index dd02586c..0ed3e6d2 100644 --- a/src/irr_v3d.h +++ b/src/irr_v3d.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include typedef core::vector3df v3f; +typedef core::vector3d v3d; typedef core::vector3d v3s16; typedef core::vector3d v3u16; typedef core::vector3d v3s32; diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index c3d60224..9847cb6f 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -13,11 +13,11 @@ void ItemStackMetadata::serialize(std::ostream &os) const { std::ostringstream os2; os2 << DESERIALIZE_START; - for (StringMap::const_iterator - it = m_stringvars.begin(); - it != m_stringvars.end(); ++it) { - os2 << it->first << DESERIALIZE_KV_DELIM - << it->second << DESERIALIZE_PAIR_DELIM; + for (StringMap::const_iterator it = m_stringvars.begin(); it != m_stringvars.end(); + ++it) { + if (!(*it).first.empty() || !(*it).second.empty()) + os2 << (*it).first << DESERIALIZE_KV_DELIM + << (*it).second << DESERIALIZE_PAIR_DELIM; } os << serializeJsonStringIfNeeded(os2.str()); } @@ -28,16 +28,18 @@ void ItemStackMetadata::deSerialize(std::istream &is) m_stringvars.clear(); - if (!in.empty() && in[0] == DESERIALIZE_START) { - Strfnd fnd(in); - fnd.to(1); - while (!fnd.at_end()) { - std::string name = fnd.next(DESERIALIZE_KV_DELIM_STR); - std::string var = fnd.next(DESERIALIZE_PAIR_DELIM_STR); - m_stringvars[name] = var; + if (!in.empty()) { + if (in[0] == DESERIALIZE_START) { + Strfnd fnd(in); + fnd.to(1); + while (!fnd.at_end()) { + std::string name = fnd.next(DESERIALIZE_KV_DELIM_STR); + std::string var = fnd.next(DESERIALIZE_PAIR_DELIM_STR); + m_stringvars[name] = var; + } + } else { + // BACKWARDS COMPATIBILITY + m_stringvars[""] = in; } - } else { - // BACKWARDS COMPATIBILITY - m_stringvars[""] = in; } } diff --git a/src/localplayer.cpp b/src/localplayer.cpp index b8c13d23..7b77ab42 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., LocalPlayer::LocalPlayer(Client *client, const char *name): Player(name, client->idef()), - parent(0), + parent(NULL), hp(PLAYER_MAX_HP), isAttached(false), touching_ground(false), @@ -53,8 +53,8 @@ LocalPlayer::LocalPlayer(Client *client, const char *name): overridePosition(v3f(0,0,0)), last_position(v3f(0,0,0)), last_speed(v3f(0,0,0)), - last_pitch(0), - last_yaw(0), + last_pitch(0.0f), + last_yaw(0.0f), last_keyPressed(0), last_camera_fov(0), last_wanted_range(0), @@ -67,13 +67,12 @@ LocalPlayer::LocalPlayer(Client *client, const char *name): hurt_tilt_timer(0.0f), hurt_tilt_strength(0.0f), m_position(0,0,0), - m_sneak_node(32767,32767,32767), - m_sneak_node_bb_ymax(0), // To support temporary option for old move code - m_sneak_node_bb_top(0,0,0,0,0,0), + m_sneak_node(32767, 32767, 32767), + m_sneak_node_bb_top(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), m_sneak_node_exists(false), - m_need_to_get_new_sneak_node(true), m_sneak_ladder_detected(false), - m_ledge_detected(false), + m_sneak_node_bb_ymax(0.0f), + m_need_to_get_new_sneak_node(true), m_old_node_below(32767,32767,32767), m_old_node_below_type("air"), m_can_jump(false), @@ -96,91 +95,142 @@ LocalPlayer::~LocalPlayer() { } -static aabb3f getTopBoundingBox(const std::vector &nodeboxes) +static aabb3f getNodeBoundingBox(const std::vector &nodeboxes) { + if (nodeboxes.size() == 0) + return aabb3f(0, 0, 0, 0, 0, 0); + aabb3f b_max; - b_max.reset(-BS, -BS, -BS); - for (std::vector::const_iterator it = nodeboxes.begin(); - it != nodeboxes.end(); ++it) { - aabb3f box = *it; - if (box.MaxEdge.Y > b_max.MaxEdge.Y) - b_max = box; - else if (box.MaxEdge.Y == b_max.MaxEdge.Y) - b_max.addInternalBox(box); - } - return aabb3f(v3f(b_max.MinEdge.X, b_max.MaxEdge.Y, b_max.MinEdge.Z), b_max.MaxEdge); + + std::vector::const_iterator it = nodeboxes.begin(); + b_max = aabb3f(it->MinEdge, it->MaxEdge); + + ++it; + for (; it != nodeboxes.end(); ++it) + b_max.addInternalBox(*it); + + return b_max; } -#define GETNODE(map, p3, v2, y, valid) \ - (map)->getNodeNoEx((p3) + v3s16((v2).X, y, (v2).Y), valid) - -// pos is the node the player is standing inside(!) -static bool detectSneakLadder(Map *map, INodeDefManager *nodemgr, v3s16 pos) +bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, + const v3f &sneak_max) { - // Detects a structure known as "sneak ladder" or "sneak elevator" - // that relies on bugs to provide a fast means of vertical transportation, - // the bugs have since been fixed but this function remains to keep it working. - // NOTE: This is just entirely a huge hack and causes way too many problems. - bool is_valid_position; + static const v3s16 dir9_center[9] = { + v3s16( 0, 0, 0), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + v3s16( 0, 0, 1), + v3s16( 0, 0, -1), + v3s16( 1, 0, 1), + v3s16(-1, 0, 1), + v3s16( 1, 0, -1), + v3s16(-1, 0, -1) + }; + + INodeDefManager *nodemgr = m_client->ndef(); MapNode node; - // X/Z vectors for 4 neighboring nodes - static const v2s16 vecs[] = { v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1) }; + bool is_valid_position; + bool new_sneak_node_exists = m_sneak_node_exists; - for (u16 i = 0; i < ARRLEN(vecs); i++) { - const v2s16 vec = vecs[i]; + // We want the top of the sneak node to be below the players feet + f32 position_y_mod = 0.05 * BS; + if (m_sneak_node_exists) + position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod; - // walkability of bottom & top node should differ - node = GETNODE(map, pos, vec, 0, &is_valid_position); - if (!is_valid_position) - continue; - bool w = nodemgr->get(node).walkable; - node = GETNODE(map, pos, vec, 1, &is_valid_position); - if (!is_valid_position || w == nodemgr->get(node).walkable) + // Get position of current standing node + const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); + + if (current_node != m_sneak_node) { + new_sneak_node_exists = false; + } else { + node = map->getNodeNoEx(current_node, &is_valid_position); + if (!is_valid_position || !nodemgr->get(node).walkable) + new_sneak_node_exists = false; + } + + // Keep old sneak node + if (new_sneak_node_exists) + return true; + + // Get new sneak node + m_sneak_ladder_detected = false; + f32 min_distance_f = 100000.0 * BS; + + for (s16 d = 0; d < 9; d++) { + const v3s16 p = current_node + dir9_center[d]; + const v3f pf = intToFloat(p, BS); + const v2f diff(position.X - pf.X, position.Z - pf.Z); + f32 distance_f = diff.getLength(); + + if (distance_f > min_distance_f || + fabs(diff.X) > (.5 + .1) * BS + sneak_max.X || + fabs(diff.Y) > (.5 + .1) * BS + sneak_max.Z) continue; - // check one more node above OR below with corresponding walkability - node = GETNODE(map, pos, vec, -1, &is_valid_position); - bool ok = is_valid_position && w != nodemgr->get(node).walkable; - if (!ok) { - node = GETNODE(map, pos, vec, 2, &is_valid_position); - ok = is_valid_position && w == nodemgr->get(node).walkable; + + // The node to be sneaked on has to be walkable + node = map->getNodeNoEx(p, &is_valid_position); + if (!is_valid_position || !nodemgr->get(node).walkable) + continue; + // And the node(s) above have to be nonwalkable + bool ok = true; + if (!physics_override_sneak_glitch) { + u16 height = ceilf( + (m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS + ); + for (u16 y = 1; y <= height; y++) { + node = map->getNodeNoEx(p + v3s16(0, y, 0), &is_valid_position); + if (!is_valid_position || nodemgr->get(node).walkable) { + ok = false; + break; + } + } + } else { + // legacy behaviour: check just one node + node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position); + ok = is_valid_position && !nodemgr->get(node).walkable; } + if (!ok) + continue; - if (ok) - return true; + min_distance_f = distance_f; + m_sneak_node = p; + new_sneak_node_exists = true; } - return false; -} + if (!new_sneak_node_exists) + return false; -static bool detectLedge(Map *map, INodeDefManager *nodemgr, v3s16 pos) -{ - bool is_valid_position; - MapNode node; - // X/Z vectors for 4 neighboring nodes - static const v2s16 vecs[] = {v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1)}; + // Update saved top bounding box of sneak node + node = map->getNodeNoEx(m_sneak_node); + std::vector nodeboxes; + node.getCollisionBoxes(nodemgr, &nodeboxes); + m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes); - for (u16 i = 0; i < ARRLEN(vecs); i++) { - const v2s16 vec = vecs[i]; - - node = GETNODE(map, pos, vec, 1, &is_valid_position); + if (physics_override_sneak_glitch) { + // Detect sneak ladder: + // Node two meters above sneak node must be solid + node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0), + &is_valid_position); if (is_valid_position && nodemgr->get(node).walkable) { - // Ledge exists - node = GETNODE(map, pos, vec, 2, &is_valid_position); - if (is_valid_position && !nodemgr->get(node).walkable) - // Space above ledge exists - return true; + // Node three meters above: must be non-solid + node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0), + &is_valid_position); + m_sneak_ladder_detected = is_valid_position && + !nodemgr->get(node).walkable; } } - - return false; + return true; } -#undef GETNODE - void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, std::vector *collision_info) { + if (!collision_info || collision_info->empty()) { + // Node below the feet, update each ClientEnvironment::step() + m_standing_node = floatToInt(m_position, BS) - v3s16(0, 1, 0); + } + // Temporary option for old move code if (!physics_override_new_move) { old_move(dtime, env, pos_max_d, collision_info); @@ -193,10 +243,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, v3f position = getPosition(); // Copy parent position if local player is attached - if(isAttached) - { + if (isAttached) { setPosition(overridePosition); - m_sneak_node_exists = false; return; } @@ -204,11 +252,11 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, bool fly_allowed = m_client->checkLocalPrivilege("fly"); bool noclip = m_client->checkLocalPrivilege("noclip") && g_settings->getBool("noclip"); - bool free_move = noclip && fly_allowed && g_settings->getBool("free_move"); - if (free_move) { + bool free_move = g_settings->getBool("free_move") && fly_allowed; + + if (noclip && free_move) { position += m_speed * dtime; setPosition(position); - m_sneak_node_exists = false; return; } @@ -279,7 +327,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, || nodemgr->get(node2.getContent()).climbable) && !free_move; } - /* Collision uncertainty radius Make it a bit larger than the maximum distance of movement @@ -291,49 +338,109 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // This should always apply, otherwise there are glitches sanity_check(d > pos_max_d); + // TODO: this shouldn't be hardcoded but transmitted from server + float player_stepheight = (touching_ground) ? (BS * 0.6f) : (BS * 0.2f); + +#if defined(__ANDROID__) || defined(__IOS__) + if (touching_ground) + player_stepheight += (0.6f * BS); +#endif + + v3f accel_f = v3f(0,0,0); + + collisionMoveResult result = collisionMoveSimple(env, m_client, + pos_max_d, m_collisionbox, player_stepheight, dtime, + &position, &m_speed, accel_f); + + bool could_sneak = control.sneak && !free_move && !in_liquid && + !is_climbing && physics_override_sneak; + + // Add new collisions to the vector + if (collision_info && !free_move) { + v3f diff = intToFloat(m_standing_node, BS) - position; + f32 distance = diff.getLength(); + // Force update each ClientEnvironment::step() + bool is_first = collision_info->empty(); + + for (std::vector::const_iterator + colinfo = result.collisions.begin(); + colinfo != result.collisions.end(); ++colinfo) { + collision_info->push_back(*colinfo); + + if (colinfo->type != COLLISION_NODE || + colinfo->new_speed.Y != 0 || + (could_sneak && m_sneak_node_exists)) + continue; + + diff = intToFloat(colinfo->node_p, BS) - position; + + // Find nearest colliding node + f32 len = diff.getLength(); + if (is_first || len < distance) { + m_standing_node = colinfo->node_p; + distance = len; + } + } + } + + /* + If the player's feet touch the topside of any node, this is + set to true. + + Player is allowed to jump when this is true. + */ + bool touching_ground_was = touching_ground; + touching_ground = result.touching_ground; + bool sneak_can_jump = false; + // Max. distance (X, Z) over border for sneaking determined by collision box // * 0.49 to keep the center just barely on the node v3f sneak_max = m_collisionbox.getExtent() * 0.49; + if (m_sneak_ladder_detected) { // restore legacy behaviour (this makes the m_speed.Y hack necessary) sneak_max = v3f(0.4 * BS, 0, 0.4 * BS); } /* - If sneaking, keep in range from the last walked node and don't - fall off from it + If sneaking, keep on top of last walked node and don't fall off */ - if (control.sneak && m_sneak_node_exists && - !(fly_allowed && g_settings->getBool("free_move")) && - !in_liquid && !is_climbing && - physics_override_sneak) { + if (could_sneak && m_sneak_node_exists) { const v3f sn_f = intToFloat(m_sneak_node, BS); const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge; const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge; const v3f old_pos = position; const v3f old_speed = m_speed; + f32 y_diff = bmax.Y - position.Y; + m_standing_node = m_sneak_node; - position.X = rangelim(position.X, + // (BS * 0.6f) is the basic stepheight while standing on ground + if (y_diff < BS * 0.6f) { + // Only center player when they're on the node + position.X = rangelim(position.X, bmin.X - sneak_max.X, bmax.X + sneak_max.X); - position.Z = rangelim(position.Z, + position.Z = rangelim(position.Z, bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z); - if (position.X != old_pos.X) - m_speed.X = 0; - if (position.Z != old_pos.Z) - m_speed.Z = 0; - - // Because we keep the player collision box on the node, limiting - // position.Y is not necessary but useful to prevent players from - // being inside a node if sneaking on e.g. the lower part of a stair - if (!m_sneak_ladder_detected) { - position.Y = MYMAX(position.Y, bmax.Y); - } else { - // legacy behaviour that sometimes causes some weird slow sinking - m_speed.Y = MYMAX(m_speed.Y, 0); + if (position.X != old_pos.X) + m_speed.X = 0; + if (position.Z != old_pos.Z) + m_speed.Z = 0; } - if (collision_info != NULL && + if (y_diff > 0 && m_speed.Y < 0 && + (physics_override_sneak_glitch || y_diff < BS * 0.6f)) { + // Move player to the maximal height when falling or when + // the ledge is climbed on the next step. + position.Y = bmax.Y; + m_speed.Y = 0; + } + + // Allow jumping on node edges while sneaking + if (m_speed.Y == 0 || m_sneak_ladder_detected) + sneak_can_jump = true; + + if (collision_info && m_speed.Y - old_speed.Y > BS) { // Collide with sneak node, report fall damage CollisionInfo sn_info; @@ -344,144 +451,24 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, } } - // TODO: this shouldn't be hardcoded but transmitted from server - float player_stepheight = (touching_ground) ? (BS * 0.6f) : (BS * 0.2f); - -#if defined(__ANDROID__) || defined(__IOS__) - player_stepheight += (0.6f * BS); -#endif - - v3f accel_f = v3f(0,0,0); - - collisionMoveResult result = collisionMoveSimple(env, m_client, - pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); - /* - If the player's feet touch the topside of any node, this is - set to true. - - Player is allowed to jump when this is true. + Find the next sneak node if necessary */ - bool touching_ground_was = touching_ground; - touching_ground = result.touching_ground; + bool new_sneak_node_exists = false; - // We want the top of the sneak node to be below the players feet - f32 position_y_mod; - if (m_sneak_node_exists) - position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - 0.05 * BS; - else - position_y_mod = (0.5 - 0.05) * BS; - v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); - /* - Check the nodes under the player to see from which node the - player is sneaking from, if any. If the node from under - the player has been removed, the player falls. - */ - if (m_sneak_node_exists && - nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" && - m_old_node_below_type != "air") { - // Old node appears to have been removed; that is, - // it wasn't air before but now it is - m_need_to_get_new_sneak_node = false; - m_sneak_node_exists = false; - } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") { - // We are on something, so make sure to recalculate the sneak - // node. - m_need_to_get_new_sneak_node = true; - } - - if (m_need_to_get_new_sneak_node && physics_override_sneak) { - v2f player_p2df(position.X, position.Z); - f32 min_distance_f = 100000.0 * BS; - v3s16 new_sneak_node = m_sneak_node; - for(s16 x=-1; x<=1; x++) - for(s16 z=-1; z<=1; z++) - { - v3s16 p = current_node + v3s16(x,0,z); - v3f pf = intToFloat(p, BS); - v2f node_p2df(pf.X, pf.Z); - f32 distance_f = player_p2df.getDistanceFrom(node_p2df); - - if (distance_f > min_distance_f || - fabs(player_p2df.X-node_p2df.X) > (.5+.1)*BS + sneak_max.X || - fabs(player_p2df.Y-node_p2df.Y) > (.5+.1)*BS + sneak_max.Z) - continue; - - - // The node to be sneaked on has to be walkable - node = map->getNodeNoEx(p, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) - continue; - // And the node(s) above have to be nonwalkable - bool ok = true; - if (!physics_override_sneak_glitch) { - u16 height = ceilf( - (m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS - ); - for (u16 y = 1; y <= height; y++) { - node = map->getNodeNoEx(p + v3s16(0,y,0), &is_valid_position); - if (!is_valid_position || nodemgr->get(node).walkable) { - ok = false; - break; - } - } - } else { - // legacy behaviour: check just one node - node = map->getNodeNoEx(p + v3s16(0,1,0), &is_valid_position); - ok = is_valid_position && !nodemgr->get(node).walkable; - } - if (!ok) - continue; - - min_distance_f = distance_f; - new_sneak_node = p; - } - - bool sneak_node_found = (min_distance_f < 100000.0 * BS); - m_sneak_node = new_sneak_node; - m_sneak_node_exists = sneak_node_found; - - if (sneak_node_found) { - // Update saved top bounding box of sneak node - MapNode n = map->getNodeNoEx(m_sneak_node); - std::vector nodeboxes; - n.getCollisionBoxes(nodemgr, &nodeboxes); - m_sneak_node_bb_top = getTopBoundingBox(nodeboxes); - - m_sneak_ladder_detected = physics_override_sneak_glitch && - detectSneakLadder(map, nodemgr, floatToInt(position, BS)); - } else { - m_sneak_ladder_detected = false; - } - } - - /* - If 'sneak glitch' enabled detect ledge for old sneak-jump - behaviour of climbing onto a ledge 2 nodes up. - */ - if (physics_override_sneak_glitch && control.sneak && control.jump) - m_ledge_detected = detectLedge(map, nodemgr, floatToInt(position, BS)); + if (could_sneak) + new_sneak_node_exists = updateSneakNode(map, position, sneak_max); /* Set new position but keep sneak node set */ - bool sneak_node_exists = m_sneak_node_exists; setPosition(position); - m_sneak_node_exists = sneak_node_exists; + m_sneak_node_exists = new_sneak_node_exists; /* Report collisions */ - // Dont report if flying - if(collision_info && !(g_settings->getBool("free_move") && fly_allowed)) { - for(size_t i=0; ipush_back(info); - } - } - if(!result.standing_on_object && !touching_ground_was && touching_ground) { MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround"); m_client->event()->put(e); @@ -501,22 +488,15 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, } } - /* - Update the node last under the player - */ - m_old_node_below = floatToInt(position - v3f(0,BS/2,0), BS); - m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name; - /* Check properties of the node on which the player is standing */ - const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos())); + const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(m_standing_node)); // Determine if jumping is possible - m_can_jump = touching_ground && !in_liquid; + m_can_jump = (touching_ground && !in_liquid && !is_climbing) + || sneak_can_jump; if (itemgroup_get(f.groups, "disable_jump")) m_can_jump = false; - else if (control.sneak && m_sneak_ladder_detected && !in_liquid && !is_climbing) - m_can_jump = true; // Jump key pressed while jumping off from a bouncy block if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && @@ -705,17 +685,8 @@ void LocalPlayer::applyControl(float dtime) */ v3f speedJ = getSpeed(); if(speedJ.Y >= -0.5 * BS) { - if (m_ledge_detected) { - // Limit jump speed to a minimum that allows - // jumping up onto a ledge 2 nodes up. - speedJ.Y = movement_speed_jump * - MYMAX(physics_override_jump, 1.3f); - setSpeed(speedJ); - m_ledge_detected = false; - } else { - speedJ.Y = movement_speed_jump * physics_override_jump; - setSpeed(speedJ); - } + speedJ.Y = movement_speed_jump * physics_override_jump; + setSpeed(speedJ); MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_client->event()->put(e); @@ -772,7 +743,7 @@ v3s16 LocalPlayer::getStandingNodePos() { if(m_sneak_node_exists) return m_sneak_node; - return floatToInt(getPosition() - v3f(0, BS, 0), BS); + return m_standing_node; } v3s16 LocalPlayer::getFootstepNodePos() @@ -958,7 +929,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, // this shouldn't be hardcoded but transmitted from server float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2); -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) player_stepheight += (0.6 * BS); #endif diff --git a/src/localplayer.h b/src/localplayer.h index 84310414..37afad02 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "environment.h" +#include "constants.h" #include class Client; @@ -46,6 +47,8 @@ public: ClientActiveObject *parent; + // Initialize hp to 0, so that no hearts will be shown if server + // doesn't support health points u16 hp; bool isAttached; bool touching_ground; @@ -108,7 +111,7 @@ public: void setCAO(GenericCAO *toset) { - assert(m_cao == NULL); // Pre-condition + assert(!m_cao); // Pre-condition m_cao = toset; } @@ -140,30 +143,31 @@ public: private: void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); void accelerateVertical(const v3f &target_speed, const f32 max_increase); + bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max); v3f m_position; + v3s16 m_standing_node; v3s16 m_sneak_node; - // Stores the max player uplift by m_sneak_node - // To support temporary option for old move code - f32 m_sneak_node_bb_ymax; // Stores the top bounding box of m_sneak_node aabb3f m_sneak_node_bb_top; // Whether the player is allowed to sneak bool m_sneak_node_exists; - // Whether recalculation of m_sneak_node and its top bbox is needed - bool m_need_to_get_new_sneak_node; // Whether a "sneak ladder" structure is detected at the players pos // see detectSneakLadder() in the .cpp for more info (always false if disabled) bool m_sneak_ladder_detected; - // Whether a 2-node-up ledge is detected at the players pos, - // see detectLedge() in the .cpp for more info (always false if disabled). - bool m_ledge_detected; + // ***** Variables for temporary option of the old move code ***** + // Stores the max player uplift by m_sneak_node + f32 m_sneak_node_bb_ymax; + // Whether recalculation of m_sneak_node and its top bbox is needed + bool m_need_to_get_new_sneak_node; // Node below player, used to determine whether it has been removed, // and its old type v3s16 m_old_node_below; std::string m_old_node_below_type; + // ***** End of variables for temporary option ***** + bool m_can_jump; u16 m_breath; f32 m_yaw; diff --git a/src/log.cpp b/src/log.cpp index 29e2fbfb..ac46c6a0 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -373,13 +373,10 @@ void StringBuffer::push_back(char c) flush(std::string(buffer, buffer_index)); buffer_index = 0; } else { - int index = buffer_index; - buffer[index++] = c; - if (index >= BUFFER_LENGTH) { + buffer[buffer_index++] = c; + if (buffer_index >= BUFFER_LENGTH) { flush(std::string(buffer, buffer_index)); buffer_index = 0; - } else { - buffer_index = index; } } } diff --git a/src/map.cpp b/src/map.cpp index 4b46335f..a63906d2 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1377,11 +1377,6 @@ s16 ServerMap::getWaterLevel() return getMapgenParams()->water_level; } -bool ServerMap::saoPositionOverLimit(const v3f &p) -{ - return getMapgenParams()->saoPosOverLimit(p); -} - bool ServerMap::blockpos_over_mapgen_limit(v3s16 p) { const s16 mapgen_limit_bp = rangelim( @@ -2734,6 +2729,7 @@ void MMVManip::blitBackAll(std::map *modified_blocks, continue; block->copyFrom(*this); + block->raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_VMANIP); if(modified_blocks) (*modified_blocks)[p] = block; diff --git a/src/map.h b/src/map.h index 70cc94e5..675ca829 100644 --- a/src/map.h +++ b/src/map.h @@ -377,8 +377,6 @@ public: */ ServerMapSector *createSector(v2s16 p); - bool saoPositionOverLimit(const v3f &p); - /* Blocks are generated by using these and makeBlock(). */ diff --git a/src/mapblock.h b/src/mapblock.h index c6f93b6d..0b8b9c26 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -122,7 +122,8 @@ public: #define MOD_REASON_STATIC_DATA_REMOVED (1 << 16) #define MOD_REASON_STATIC_DATA_CHANGED (1 << 17) #define MOD_REASON_EXPIRE_DAYNIGHTDIFF (1 << 18) -#define MOD_REASON_UNKNOWN (1 << 19) +#define MOD_REASON_VMANIP (1 << 19) +#define MOD_REASON_UNKNOWN (1 << 20) //// //// MapBlock itself diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 37508dfa..2dfe890b 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -815,7 +815,16 @@ void MapgenBasic::dustTopNodes() } content_t c = vm->m_data[vi].getContent(); - if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE && c != biome->c_dust) { + NodeDrawType dtype = ndef->get(c).drawtype; + // Only place on walkable cubic non-liquid nodes + // Dust check needed due to vertical overgeneration + if ((dtype == NDT_NORMAL || + dtype == NDT_ALLFACES_OPTIONAL || + dtype == NDT_GLASSLIKE_FRAMED_OPTIONAL || + dtype == NDT_GLASSLIKE || + dtype == NDT_GLASSLIKE_FRAMED || + dtype == NDT_ALLFACES) && + ndef->get(c).walkable && c != biome->c_dust) { vm->m_area.add_y(em, vi, 1); vm->m_data[vi] = MapNode(biome->c_dust); } @@ -979,8 +988,7 @@ bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id) void GenerateNotifier::getEvents( - std::map > &event_map, - bool peek_events) + std::map > &event_map) { std::list::iterator it; @@ -992,9 +1000,12 @@ void GenerateNotifier::getEvents( event_map[name].push_back(gn.pos); } +} - if (!peek_events) - m_notify_events.clear(); + +void GenerateNotifier::clearEvents() +{ + m_notify_events.clear(); } @@ -1055,16 +1066,15 @@ void MapgenParams::writeParams(Settings *settings) const bparams->writeParams(settings); } -// Calculate edges of outermost generated mapchunks (less than -// 'mapgen_limit'), and corresponding exact limits for SAO entities. + +// Calculate exact edges of the outermost mapchunks that are within the +// set 'mapgen_limit'. void MapgenParams::calcMapgenEdges() { // Central chunk offset, in blocks s16 ccoff_b = -chunksize / 2; - // Chunksize, in nodes s32 csize_n = chunksize * MAP_BLOCKSIZE; - // Minp/maxp of central chunk, in nodes s16 ccmin = ccoff_b * MAP_BLOCKSIZE; s16 ccmax = ccmin + csize_n - 1; @@ -1083,25 +1093,17 @@ void MapgenParams::calcMapgenEdges() s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0); s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0); // Mapgen edges, in nodes - // These values may be useful later as additional class members - s16 mapgen_edge_min = ccmin - numcmin * csize_n; - s16 mapgen_edge_max = ccmax + numcmax * csize_n; - // SAO position limits, in Irrlicht units - m_sao_limit_min = mapgen_edge_min * BS - 3.0f; - m_sao_limit_max = mapgen_edge_max * BS + 3.0f; + mapgen_edge_min = ccmin - numcmin * csize_n; + mapgen_edge_max = ccmax + numcmax * csize_n; + + m_mapgen_edges_calculated = true; } -bool MapgenParams::saoPosOverLimit(const v3f &p) +s32 MapgenParams::getSpawnRangeMax() { - if (!m_sao_limit_calculated) { + if (!m_mapgen_edges_calculated) calcMapgenEdges(); - m_sao_limit_calculated = true; - } - return p.X < m_sao_limit_min || - p.X > m_sao_limit_max || - p.Y < m_sao_limit_min || - p.Y > m_sao_limit_max || - p.Z < m_sao_limit_min || - p.Z > m_sao_limit_max; + + return MYMIN(-mapgen_edge_min, mapgen_edge_max); } diff --git a/src/mapgen.h b/src/mapgen.h index 21dbc40a..f8c1c231 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -101,8 +101,8 @@ public: void setNotifyOnDecoIds(std::set *notify_on_deco_ids); bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0); - void getEvents(std::map > &event_map, - bool peek_events=false); + void getEvents(std::map > &event_map); + void clearEvents(); private: u32 m_notify_on; @@ -132,6 +132,9 @@ struct MapgenParams { BiomeParams *bparams; + s16 mapgen_edge_min; + s16 mapgen_edge_max; + MapgenParams() : mgtype(MAPGEN_DEFAULT), chunksize(5), @@ -140,9 +143,10 @@ struct MapgenParams { mapgen_limit(MAX_MAP_GENERATION_LIMIT), flags(MG_CAVES | MG_LIGHT | MG_DECORATIONS), bparams(NULL), - m_sao_limit_min(MAX_MAP_GENERATION_LIMIT * BS), - m_sao_limit_max(MAX_MAP_GENERATION_LIMIT * BS), - m_sao_limit_calculated(false) + + mapgen_edge_min(-MAX_MAP_GENERATION_LIMIT), + mapgen_edge_max(MAX_MAP_GENERATION_LIMIT), + m_mapgen_edges_calculated(false) { } @@ -151,13 +155,12 @@ struct MapgenParams { virtual void readParams(const Settings *settings); virtual void writeParams(Settings *settings) const; - bool saoPosOverLimit(const v3f &p); + s32 getSpawnRangeMax(); + private: void calcMapgenEdges(); - float m_sao_limit_min; - float m_sao_limit_max; - bool m_sao_limit_calculated; + bool m_mapgen_edges_calculated; }; diff --git a/src/mapgen_v7.cpp b/src/mapgen_v7.cpp index 397474bf..fd4f983b 100644 --- a/src/mapgen_v7.cpp +++ b/src/mapgen_v7.cpp @@ -57,13 +57,17 @@ MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge) this->spflags = params->spflags; this->cave_width = params->cave_width; this->float_mount_density = params->float_mount_density; - this->float_mount_height = params->float_mount_height; this->floatland_level = params->floatland_level; this->shadow_limit = params->shadow_limit; this->cavern_limit = params->cavern_limit; this->cavern_taper = params->cavern_taper; this->cavern_threshold = params->cavern_threshold; + // This is to avoid a divide-by-zero. + // Parameter will be saved to map_meta.txt in limited form. + params->float_mount_height = MYMAX(params->float_mount_height, 1.0f); + this->float_mount_height = params->float_mount_height; + // 2D noise noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z); noise_terrain_alt = new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z); @@ -376,7 +380,8 @@ float MapgenV7::baseTerrainLevelFromMap(int index) bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z) { - float mnt_h_n = NoisePerlin2D(&noise_mount_height->np, x, z, seed); + float mnt_h_n = + MYMAX(NoisePerlin2D(&noise_mount_height->np, x, z, seed), 1.0f); float density_gradient = -((float)y / mnt_h_n); float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed); @@ -386,7 +391,7 @@ bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z) bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) { - float mounthn = noise_mount_height->result[idx_xz]; + float mounthn = MYMAX(noise_mount_height->result[idx_xz], 1.0f); float density_gradient = -((float)y / mounthn); float mountn = noise_mountain->result[idx_xyz]; @@ -415,7 +420,8 @@ void MapgenV7::floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, float n_base = noise_floatland_base->result[idx_xz]; if (n_base > 0.0f) { - float n_base_height = noise_float_base_height->result[idx_xz]; + float n_base_height = + MYMAX(noise_float_base_height->result[idx_xz], 1.0f); float amp = n_base * n_base_height; float ridge = n_base_height / 3.0f; base_min = floatland_level - amp / 1.5f; diff --git a/src/mapnode.cpp b/src/mapnode.cpp index b0ae1926..aea5d330 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -249,18 +249,15 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, int facedir = n.getFaceDir(nodemgr); u8 axisdir = facedir>>2; facedir&=0x03; - for(std::vector::const_iterator + for (std::vector::const_iterator i = fixed.begin(); - i != fixed.end(); ++i) - { + i != fixed.end(); ++i) { aabb3f box = *i; - if (nodebox.type == NODEBOX_LEVELED) { - box.MaxEdge.Y = -BS/2 + BS*((float)1/LEVELED_MAX) * n.getLevel(nodemgr); - } + if (nodebox.type == NODEBOX_LEVELED) + box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * BS; - switch (axisdir) - { + switch (axisdir) { case 0: if(facedir == 1) { diff --git a/src/mapnode.h b/src/mapnode.h index ddea69f4..7d156558 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -103,8 +103,8 @@ enum Rotation { #define LIQUID_INFINITY_MASK 0x80 //0b10000000 -// mask for param2, now as for liquid -#define LEVELED_MASK 0x3F +// mask for leveled nodebox param2 +#define LEVELED_MASK 0x7F #define LEVELED_MAX LEVELED_MASK diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 04b56d19..5b659346 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -580,10 +580,6 @@ void Client::handleCommand_MovePlayer(NetworkPacket* pkt) event.player_force_move.pitch = pitch; event.player_force_move.yaw = yaw; m_client_event_queue.push(event); - - // Ignore damage for a few seconds, so that the player doesn't - // get damage from falling on ground - m_ignore_damage_timer = 3.0; } void Client::handleCommand_DeathScreen(NetworkPacket* pkt) @@ -1172,9 +1168,21 @@ void Client::handleCommand_HudSetParam(NetworkPacket* pkt) player->hud_hotbar_itemcount = hotbar_itemcount; } else if (param == HUD_PARAM_HOTBAR_IMAGE) { + // If value not empty verify image exists in texture source + if (value != "" && !getTextureSource()->isKnownSourceImage(value)) { + errorstream << "Server sent wrong Hud hotbar image (sent value: '" + << value << "')" << std::endl; + return; + } player->hotbar_image = value; } else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { + // If value not empty verify image exists in texture source + if (value != "" && !getTextureSource()->isKnownSourceImage(value)) { + errorstream << "Server sent wrong Hud hotbar selected image (sent value: '" + << value << "')" << std::endl; + return; + } player->hotbar_selected_image = value; } } diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index 1a34ab05..24b5f263 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -58,9 +58,11 @@ void NetworkPacket::putRawPacket(u8 *data, u32 datasize, u16 peer_id) m_datasize = datasize - 2; m_peer_id = peer_id; + m_data.resize(m_datasize); + // split command and datas m_command = readU16(&data[0]); - m_data = std::vector(&data[2], &data[2 + m_datasize]); + memcpy(m_data.data(), &data[2], m_datasize); } const char* NetworkPacket::getString(u32 from_offset) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 7bc222c7..51e4c890 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -211,7 +211,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt) // Enforce user limit. // Don't enforce for users that have some admin right - if (m_clients.getClientIDs(CS_Created).size() >= g_settings->getU16("max_users") && + if (m_clients.isUserLimitReached() && !checkPriv(playername, "server") && !checkPriv(playername, "ban") && !checkPriv(playername, "privs") && @@ -520,7 +520,7 @@ void Server::handleCommand_Init_Legacy(NetworkPacket* pkt) // Enforce user limit. // Don't enforce for users that have some admin right - if (m_clients.getClientIDs(CS_Created).size() >= g_settings->getU16("max_users") && + if (m_clients.isUserLimitReached() && !checkPriv(playername, "server") && !checkPriv(playername, "ban") && !checkPriv(playername, "privs") && @@ -943,6 +943,18 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) (ma->to_inv.type == InventoryLocation::PLAYER) && (ma->to_inv.name == player->getName()); + InventoryLocation *remote = from_inv_is_current_player ? + &ma->to_inv : &ma->from_inv; + + // Check for out-of-range interaction + if (remote->type == InventoryLocation::NODEMETA) { + v3f node_pos = intToFloat(remote->p, BS); + v3f player_pos = player->getPlayerSAO()->getBasePosition(); + f32 d = player_pos.getDistanceFrom(node_pos); + if (!checkInteractDistance(player, d, "inventory")) + return; + } + /* Disable moving items out of craftpreview */ @@ -1257,6 +1269,37 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt) // the previous addition has been successfully removed } +bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string what) +{ + PlayerSAO *playersao = player->getPlayerSAO(); + const InventoryList *hlist = playersao->getInventory()->getList("hand"); + const ItemDefinition &playeritem_def = + playersao->getWieldedItem().getDefinition(m_itemdef); + const ItemDefinition &hand_def = + hlist ? hlist->getItem(0).getDefinition(m_itemdef) : m_itemdef->get(""); + + float max_d = BS * playeritem_def.range; + float max_d_hand = BS * hand_def.range; + + if (max_d < 0 && max_d_hand >= 0) + max_d = max_d_hand; + else if (max_d < 0) + max_d = BS * 4.0f; + + // cube diagonal: sqrt(3) = 1.732 + if (d > max_d * 1.732) { + actionstream << "Player " << player->getName() + << " tried to access " << what + << " from too far: " + << "d=" << d <<", max_d=" << max_d + << ". ignoring." << std::endl; + // Call callbacks + m_script->on_cheat(playersao, "interacted_too_far"); + return false; + } + return true; +} + void Server::handleCommand_Interact(NetworkPacket* pkt) { /* @@ -1380,33 +1423,13 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) */ static const bool enable_anticheat = !g_settings->getBool("disable_anticheat"); if ((action == 0 || action == 2 || action == 3 || action == 4) && - (enable_anticheat && !isSingleplayer())) { + enable_anticheat && !isSingleplayer()) { float d = player_pos.getDistanceFrom(pointed_pos_under); - const ItemDefinition &playeritem_def = - playersao->getWieldedItem().getDefinition(m_itemdef); - float max_d = BS * playeritem_def.range; - InventoryList *hlist = playersao->getInventory()->getList("hand"); - const ItemDefinition &hand_def = - hlist ? (hlist->getItem(0).getDefinition(m_itemdef)) : (m_itemdef->get("")); - float max_d_hand = BS * hand_def.range; - if (max_d < 0 && max_d_hand >= 0) - max_d = max_d_hand; - else if (max_d < 0) - max_d = BS * 4.0; - // cube diagonal: sqrt(3) = 1.73 - if (d > max_d * 1.73) { - actionstream << "Player " << player->getName() - << " tried to access " << pointed.dump() - << " from too far: " - << "d=" << d <<", max_d=" << max_d - << ". ignoring." << std::endl; + if (!checkInteractDistance(player, d, pointed.dump())) { // Re-send block to revert change on client-side RemoteClient *client = getClient(pkt->getPeerId()); v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); client->SetBlockNotSent(blockpos); - // Call callbacks - m_script->on_cheat(playersao, "interacted_too_far"); - // Do nothing else return; } } @@ -1441,8 +1464,8 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) playersao->noCheatDigStart(p_under); } else if (pointed.type == POINTEDTHING_OBJECT) { - // Skip if object has been removed - if (pointed_object->m_removed) + // Skip if object can't be interacted with anymore + if (pointed_object->isGone()) return; actionstream<getName()<<" punches object " @@ -1600,8 +1623,8 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) if (pointed.type == POINTEDTHING_OBJECT) { // Right click object - // Skip if object has been removed - if (pointed_object->m_removed) + // Skip if object can't be interacted with anymore + if (pointed_object->isGone()) return; actionstream << player->getName() << " right-clicks object " diff --git a/src/nodedef.cpp b/src/nodedef.cpp index a5c7545d..132bf671 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -682,6 +682,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc switch (drawtype) { default: case NDT_NORMAL: + material_type = (alpha == 255) ? + TILE_MATERIAL_OPAQUE : TILE_MATERIAL_ALPHA; solidness = 2; break; case NDT_AIRLIKE: @@ -778,6 +780,16 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc tile_shader[j] = shdsrc->getShader("nodes_shader", material_type, drawtype); } + u8 overlay_material = material_type; + if (overlay_material == TILE_MATERIAL_OPAQUE) + overlay_material = TILE_MATERIAL_BASIC; + else if (overlay_material == TILE_MATERIAL_LIQUID_OPAQUE) + overlay_material = TILE_MATERIAL_LIQUID_TRANSPARENT; + u32 overlay_shader[6]; + for (u16 j = 0; j < 6; j++) { + overlay_shader[j] = shdsrc->getShader("nodes_shader", + overlay_material, drawtype); + } // Tiles (fill in f->tiles[]) for (u16 j = 0; j < 6; j++) { @@ -786,8 +798,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc tdef[j].backface_culling, material_type); if (tdef_overlay[j].name != "") fillTileAttribs(tsrc, &tiles[j].layers[1], &tdef_overlay[j], - tile_shader[j], tsettings.use_normal_texture, - tdef[j].backface_culling, material_type); + overlay_shader[j], tsettings.use_normal_texture, + tdef[j].backface_culling, overlay_material); } // Special tiles (fill in f->special_tiles[]) diff --git a/src/noise.cpp b/src/noise.cpp index b918c993..abd29f3e 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -130,7 +130,9 @@ s32 PcgRandom::range(s32 min, s32 max) if (max < min) throw PrngException("Invalid range (max < min)"); - u32 bound = max - min + 1; + // We have to cast to s64 because otherwise this could overflow, + // and signed overflow is undefined behavior. + u32 bound = (s64)max - (s64)min + 1; return range(bound) + min; } diff --git a/src/particles.cpp b/src/particles.cpp index 2171d3a0..dfbebd01 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -290,6 +290,59 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, ParticleSpawner::~ParticleSpawner() {} +void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, + bool is_attached, const v3f &attached_pos, float attached_yaw) +{ + v3f ppos = m_player->getPosition() / BS; + v3f pos = random_v3f(m_minpos, m_maxpos); + + // Need to apply this first or the following check + // will be wrong for attached spawners + if (is_attached) { + pos.rotateXZBy(attached_yaw); + pos += attached_pos; + } + + if (pos.getDistanceFrom(ppos) > radius) + return; + + v3f vel = random_v3f(m_minvel, m_maxvel); + v3f acc = random_v3f(m_minacc, m_maxacc); + + if (is_attached) { + // Apply attachment yaw + vel.rotateXZBy(attached_yaw); + acc.rotateXZBy(attached_yaw); + } + + float exptime = rand() / (float)RAND_MAX + * (m_maxexptime - m_minexptime) + + m_minexptime; + float size = rand() / (float)RAND_MAX + * (m_maxsize - m_minsize) + + m_minsize; + + m_particlemanager->addParticle(new Particle( + m_gamedef, + m_smgr, + m_player, + env, + pos, + vel, + acc, + exptime, + size, + m_collisiondetection, + m_collision_removal, + m_vertical, + m_texture, + v2f(0.0, 0.0), + v2f(1.0, 1.0), + m_animation, + m_glow + )); +} + void ParticleSpawner::step(float dtime, ClientEnvironment* env) { m_time += dtime; @@ -311,122 +364,33 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) } } - if (m_spawntime != 0) // Spawner exists for a predefined timespan - { - for(std::vector::iterator i = m_spawntimes.begin(); - i != m_spawntimes.end();) - { - if ((*i) <= m_time && m_amount > 0) - { + if (m_spawntime != 0) { + // Spawner exists for a predefined timespan + for (std::vector::iterator i = m_spawntimes.begin(); + i != m_spawntimes.end();) { + if ((*i) <= m_time && m_amount > 0) { m_amount--; // Pretend to, but don't actually spawn a particle if it is // attached to an unloaded object or distant from player. - if (!unloaded) { - v3f ppos = m_player->getPosition() / BS; - v3f pos = random_v3f(m_minpos, m_maxpos); + if (!unloaded) + spawnParticle(env, radius, is_attached, attached_pos, attached_yaw); - if (pos.getDistanceFrom(ppos) <= radius) { - v3f vel = random_v3f(m_minvel, m_maxvel); - v3f acc = random_v3f(m_minacc, m_maxacc); - - if (is_attached) { - // Apply attachment yaw and position - pos.rotateXZBy(attached_yaw); - pos += attached_pos; - vel.rotateXZBy(attached_yaw); - acc.rotateXZBy(attached_yaw); - } - - float exptime = rand()/(float)RAND_MAX - *(m_maxexptime-m_minexptime) - +m_minexptime; - float size = rand()/(float)RAND_MAX - *(m_maxsize-m_minsize) - +m_minsize; - - Particle* toadd = new Particle( - m_gamedef, - m_smgr, - m_player, - env, - pos, - vel, - acc, - exptime, - size, - m_collisiondetection, - m_collision_removal, - m_vertical, - m_texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0), - m_animation, - m_glow); - m_particlemanager->addParticle(toadd); - } - } i = m_spawntimes.erase(i); - } - else - { + } else { ++i; } } - } - else // Spawner exists for an infinity timespan, spawn on a per-second base - { + } else { + // Spawner exists for an infinity timespan, spawn on a per-second base + // Skip this step if attached to an unloaded object if (unloaded) return; - for (int i = 0; i <= m_amount; i++) - { - if (rand()/(float)RAND_MAX < dtime) - { - // Do not spawn particle if distant from player - v3f ppos = m_player->getPosition() / BS; - v3f pos = random_v3f(m_minpos, m_maxpos); - if (pos.getDistanceFrom(ppos) <= radius) { - v3f vel = random_v3f(m_minvel, m_maxvel); - v3f acc = random_v3f(m_minacc, m_maxacc); - - if (is_attached) { - // Apply attachment yaw and position - pos.rotateXZBy(attached_yaw); - pos += attached_pos; - vel.rotateXZBy(attached_yaw); - acc.rotateXZBy(attached_yaw); - } - - float exptime = rand()/(float)RAND_MAX - *(m_maxexptime-m_minexptime) - +m_minexptime; - float size = rand()/(float)RAND_MAX - *(m_maxsize-m_minsize) - +m_minsize; - - Particle* toadd = new Particle( - m_gamedef, - m_smgr, - m_player, - env, - pos, - vel, - acc, - exptime, - size, - m_collisiondetection, - m_collision_removal, - m_vertical, - m_texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0), - m_animation, - m_glow); - m_particlemanager->addParticle(toadd); - } - } + for (int i = 0; i <= m_amount; i++) { + if (rand() / (float)RAND_MAX < dtime) + spawnParticle(env, radius, is_attached, attached_pos, attached_yaw); } } } diff --git a/src/particles.h b/src/particles.h index 15e6d26c..56973fbf 100644 --- a/src/particles.h +++ b/src/particles.h @@ -117,7 +117,7 @@ private: class ParticleSpawner { - public: +public: ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player, @@ -144,8 +144,12 @@ class ParticleSpawner bool get_expired () { return (m_amount <= 0) && m_spawntime != 0; } - private: - ParticleManager* m_particlemanager; +private: + void spawnParticle(ClientEnvironment *env, float radius, + bool is_attached, const v3f &attached_pos, + float attached_yaw); + + ParticleManager *m_particlemanager; float m_time; IGameDef *m_gamedef; scene::ISceneManager *m_smgr; diff --git a/src/porting.cpp b/src/porting.cpp index e8c6a006..22ea7b74 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include + #include #endif #if !defined(_WIN32) #include @@ -182,20 +183,26 @@ bool detectMSVCBuildDir(const std::string &path) std::string get_sysinfo() { #ifdef _WIN32 - OSVERSIONINFO osvi; - std::ostringstream oss; - std::string tmp; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); - tmp = osvi.szCSDVersion; - std::replace(tmp.begin(), tmp.end(), ' ', '_'); - oss << "Windows/" << osvi.dwMajorVersion << "." - << osvi.dwMinorVersion; - if (osvi.szCSDVersion[0]) - oss << "-" << tmp; - oss << " "; + std::ostringstream oss; + LPSTR filePath = new char[MAX_PATH]; + UINT blockSize; + VS_FIXEDFILEINFO *fixedFileInfo; + + GetSystemDirectoryA(filePath, MAX_PATH); + PathAppendA(filePath, "kernel32.dll"); + + DWORD dwVersionSize = GetFileVersionInfoSizeA(filePath, NULL); + LPBYTE lpVersionInfo = new BYTE[dwVersionSize]; + + GetFileVersionInfoA(filePath, 0, dwVersionSize, lpVersionInfo); + VerQueryValueA(lpVersionInfo, "\\", (LPVOID *)&fixedFileInfo, &blockSize); + + oss << "Windows/" + << HIWORD(fixedFileInfo->dwProductVersionMS) << '.' // Major + << LOWORD(fixedFileInfo->dwProductVersionMS) << '.' // Minor + << HIWORD(fixedFileInfo->dwProductVersionLS) << ' '; // Build + #ifdef _WIN64 oss << "x86_64"; #else @@ -206,6 +213,9 @@ std::string get_sysinfo() oss << "x86"; #endif + delete[] lpVersionInfo; + delete[] filePath; + return oss.str(); #else struct utsname osinfo; diff --git a/src/reflowscan.cpp b/src/reflowscan.cpp index c19dabc1..2b6dd9f2 100644 --- a/src/reflowscan.cpp +++ b/src/reflowscan.cpp @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., ReflowScan::ReflowScan(Map *map, INodeDefManager *ndef) : m_map(map), m_ndef(ndef), - m_liquid_queue(nullptr) + m_liquid_queue(NULL) { } @@ -42,7 +42,7 @@ void ReflowScan::scan(MapBlock *block, UniqueQueue *liquid_queue) // scanned block. Blocks are only added to the lookup if they are really // needed. The lookup is indexed manually to use the same index in a // bit-array (of uint32 type) which stores for unloaded blocks that they - // were already fetched from Map but were actually nullptr. + // were already fetched from Map but were actually NULL. memset(m_lookup, 0, sizeof(m_lookup)); int block_idx = 1 + (1 * 9) + (1 * 3); m_lookup[block_idx] = block; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index a78b3dfa..1b5d0cca 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -43,6 +43,7 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): m_last_chat_message_sent(time(NULL)), m_chat_message_allowance(5.0f), m_message_rate_overhead(0), + m_day_night_ratio_do_override(false), hud_hotbar_image(""), hud_hotbar_selected_image("") { diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index ae763949..0222b265 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -196,6 +196,44 @@ v3f check_v3f(lua_State *L, int index) return pos; } +v3d read_v3d(lua_State *L, int index) +{ + v3d pos; + CHECK_POS_TAB(index); + lua_getfield(L, index, "x"); + pos.X = lua_tonumber(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "y"); + pos.Y = lua_tonumber(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "z"); + pos.Z = lua_tonumber(L, -1); + lua_pop(L, 1); + return pos; +} + +v3d check_v3d(lua_State *L, int index) +{ + v3d pos; + CHECK_POS_TAB(index); + lua_getfield(L, index, "x"); + CHECK_POS_COORD("x"); + pos.X = lua_tonumber(L, -1); + CHECK_FLOAT_RANGE(pos.X, "x") + lua_pop(L, 1); + lua_getfield(L, index, "y"); + CHECK_POS_COORD("y"); + pos.Y = lua_tonumber(L, -1); + CHECK_FLOAT_RANGE(pos.Y, "y") + lua_pop(L, 1); + lua_getfield(L, index, "z"); + CHECK_POS_COORD("z"); + pos.Z = lua_tonumber(L, -1); + CHECK_FLOAT_RANGE(pos.Z, "z") + lua_pop(L, 1); + return pos; +} + void push_ARGB8(lua_State *L, video::SColor color) { lua_newtable(L); @@ -234,15 +272,15 @@ void push_v3s16(lua_State *L, v3s16 p) v3s16 read_v3s16(lua_State *L, int index) { // Correct rounding at <0 - v3f pf = read_v3f(L, index); - return floatToInt(pf, 1.0); + v3d pf = read_v3d(L, index); + return doubleToInt(pf, 1.0); } v3s16 check_v3s16(lua_State *L, int index) { // Correct rounding at <0 - v3f pf = check_v3f(L, index); - return floatToInt(pf, 1.0); + v3d pf = check_v3d(L, index); + return doubleToInt(pf, 1.0); } bool read_color(lua_State *L, int index, video::SColor *color) diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index 25903217..2e6d09d8 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -178,9 +178,17 @@ void log_deprecated(lua_State *L, const std::string &message) } if (do_log) { - warningstream << message << std::endl; - // L can be NULL if we get called by log_deprecated(const std::string &msg) - // from scripting_game.cpp. + warningstream << message; + if (L) { // L can be NULL if we get called from scripting_game.cpp + lua_Debug ar; + + if (!lua_getstack(L, 2, &ar)) + FATAL_ERROR_IF(!lua_getstack(L, 1, &ar), "lua_getstack() failed"); + FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed"); + warningstream << " (at " << ar.short_src << ":" << ar.currentline << ")"; + } + warningstream << std::endl; + if (L) { if (do_error) script_error(L, LUA_ERRRUN, NULL, NULL); diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 6b4a2cd8..cf87719e 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -119,6 +119,9 @@ ScriptApiBase::ScriptApiBase() : m_environment = NULL; m_guiengine = NULL; + + // Make sure Lua uses the right locale + setlocale(LC_NUMERIC, "C"); } ScriptApiBase::~ScriptApiBase() @@ -322,6 +325,10 @@ void ScriptApiBase::objectrefGetOrCreate(lua_State *L, ObjectRef::create(L, cobj); } else { push_objectRef(L, cobj->getId()); + if (cobj->isGone()) + warningstream << "ScriptApiBase::objectrefGetOrCreate(): " + << "Pushing ObjectRef to removed/deactivated object" + << ", this is probably a bug." << std::endl; } } diff --git a/src/script/cpp_api/s_item.cpp b/src/script/cpp_api/s_item.cpp index c6818671..a87f308c 100644 --- a/src/script/cpp_api/s_item.cpp +++ b/src/script/cpp_api/s_item.cpp @@ -69,7 +69,12 @@ bool ScriptApiItem::item_OnPlace(ItemStack &item, // Call function LuaItemStack::create(L, item); - objectrefGetOrCreate(L, placer); + + if (!placer) + lua_pushnil(L); + else + objectrefGetOrCreate(L, placer); + pushPointedThing(pointed); PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if (!lua_isnil(L, -1)) { @@ -206,7 +211,8 @@ bool ScriptApiItem::item_CraftPredict(ItemStack &item, ServerActiveObject *user, // function onto the stack // If core.registered_items[name] doesn't exist, core.nodedef_default // is tried instead so unknown items can still be manipulated to some degree -bool ScriptApiItem::getItemCallback(const char *name, const char *callbackname) +bool ScriptApiItem::getItemCallback(const char *name, const char *callbackname, + const v3s16 *p) { lua_State* L = getStack(); @@ -217,10 +223,12 @@ bool ScriptApiItem::getItemCallback(const char *name, const char *callbackname) lua_getfield(L, -1, name); lua_remove(L, -2); // Remove registered_items // Should be a table - if(lua_type(L, -1) != LUA_TTABLE) - { + if (lua_type(L, -1) != LUA_TTABLE) { // Report error and clean up - errorstream << "Item \"" << name << "\" not defined" << std::endl; + errorstream << "Item \"" << name << "\" not defined"; + if (p) + errorstream << " at position " << PP(*p); + errorstream << std::endl; lua_pop(L, 1); // Try core.nodedef_default instead diff --git a/src/script/cpp_api/s_item.h b/src/script/cpp_api/s_item.h index 43512361..64f17ecb 100644 --- a/src/script/cpp_api/s_item.h +++ b/src/script/cpp_api/s_item.h @@ -53,7 +53,7 @@ protected: friend class LuaItemStack; friend class ModApiItemMod; - bool getItemCallback(const char *name, const char *callbackname); + bool getItemCallback(const char *name, const char *callbackname, const v3s16 *p = NULL); void pushPointedThing(const PointedThing& pointed); }; diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index 8e3dce56..88da302e 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -107,7 +107,7 @@ bool ScriptApiNode::node_on_punch(v3s16 p, MapNode node, INodeDefManager *ndef = getServer()->ndef(); // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "on_punch")) + if (!getItemCallback(ndef->get(node).name.c_str(), "on_punch", &p)) return false; // Call function @@ -130,7 +130,7 @@ bool ScriptApiNode::node_on_dig(v3s16 p, MapNode node, INodeDefManager *ndef = getServer()->ndef(); // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "on_dig")) + if (!getItemCallback(ndef->get(node).name.c_str(), "on_dig", &p)) return false; // Call function @@ -151,7 +151,7 @@ void ScriptApiNode::node_on_construct(v3s16 p, MapNode node) INodeDefManager *ndef = getServer()->ndef(); // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "on_construct")) + if (!getItemCallback(ndef->get(node).name.c_str(), "on_construct", &p)) return; // Call function @@ -169,7 +169,7 @@ void ScriptApiNode::node_on_destruct(v3s16 p, MapNode node) INodeDefManager *ndef = getServer()->ndef(); // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "on_destruct")) + if (!getItemCallback(ndef->get(node).name.c_str(), "on_destruct", &p)) return; // Call function @@ -187,7 +187,7 @@ bool ScriptApiNode::node_on_flood(v3s16 p, MapNode node, MapNode newnode) INodeDefManager *ndef = getServer()->ndef(); // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "on_flood")) + if (!getItemCallback(ndef->get(node).name.c_str(), "on_flood", &p)) return false; // Call function @@ -208,7 +208,7 @@ void ScriptApiNode::node_after_destruct(v3s16 p, MapNode node) INodeDefManager *ndef = getServer()->ndef(); // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "after_destruct")) + if (!getItemCallback(ndef->get(node).name.c_str(), "after_destruct", &p)) return; // Call function @@ -227,7 +227,7 @@ bool ScriptApiNode::node_on_timer(v3s16 p, MapNode node, f32 dtime) INodeDefManager *ndef = getServer()->ndef(); // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "on_timer")) + if (!getItemCallback(ndef->get(node).name.c_str(), "on_timer", &p)) return false; // Call function @@ -255,7 +255,7 @@ void ScriptApiNode::node_on_receive_fields(v3s16 p, return; // Push callback function on stack - if (!getItemCallback(ndef->get(node).name.c_str(), "on_receive_fields")) + if (!getItemCallback(ndef->get(node).name.c_str(), "on_receive_fields", &p)) return; // Call function diff --git a/src/script/cpp_api/s_nodemeta.cpp b/src/script/cpp_api/s_nodemeta.cpp index c3340383..72f0fbaa 100644 --- a/src/script/cpp_api/s_nodemeta.cpp +++ b/src/script/cpp_api/s_nodemeta.cpp @@ -45,7 +45,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove(v3s16 p, // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_move")) + if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_move", &p)) return count; // function(pos, from_list, from_index, to_list, to_index, count, player) @@ -83,7 +83,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut(v3s16 p, // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_put")) + if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_put", &p)) return stack.count; // Call function(pos, listname, index, stack, player) @@ -119,7 +119,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake(v3s16 p, // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_take")) + if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_take", &p)) return stack.count; // Call function(pos, listname, index, count, player) @@ -156,7 +156,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnMove(v3s16 p, // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_move")) + if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_move", &p)) return; // function(pos, from_list, from_index, to_list, to_index, count, player) @@ -189,7 +189,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnPut(v3s16 p, // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_put")) + if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_put", &p)) return; // Call function(pos, listname, index, stack, player) @@ -220,7 +220,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnTake(v3s16 p, // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_take")) + if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_take", &p)) return; // Call function(pos, listname, index, stack, player) diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 80880ca0..17e38acd 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -249,9 +249,8 @@ void ScriptApiSecurity::initializeSecurityClient() static const char *os_whitelist[] = { "clock", "date", - "difftime", - "time", - "setlocale", + "difftime", + "time" }; static const char *debug_whitelist[] = { "getinfo", diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index c43dc3c3..c21374a0 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -291,8 +291,7 @@ int ModApiEnvMod::l_place_node(lua_State *L) pointed.type = POINTEDTHING_NODE; pointed.node_abovesurface = pos; pointed.node_undersurface = pos + v3s16(0,-1,0); - // Place it with a NULL placer (appears in Lua as a non-functional - // ObjectRef) + // Place it with a NULL placer (appears in Lua as nil) bool success = scriptIfaceItem->item_OnPlace(item, NULL, pointed); lua_pushboolean(L, success); return 1; @@ -537,9 +536,11 @@ int ModApiEnvMod::l_get_objects_inside_radius(lua_State *L) std::vector::const_iterator iter = ids.begin(); for(u32 i = 0; iter != ids.end(); ++iter) { ServerActiveObject *obj = env->getActiveObject(*iter); - // Insert object reference into table - script->objectrefGetOrCreate(L, obj); - lua_rawseti(L, -2, ++i); + if (!obj->isGone()) { + // Insert object reference into table + script->objectrefGetOrCreate(L, obj); + lua_rawseti(L, -2, ++i); + } } return 1; } @@ -651,38 +652,47 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) INodeDefManager *ndef = getServer(L)->ndef(); v3s16 minp = read_v3s16(L, 1); v3s16 maxp = read_v3s16(L, 2); + sortBoxVerticies(minp, maxp); + + v3s16 cube = maxp - minp + 1; + // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 + if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { + luaL_error(L, "find_nodes_in_area(): area volume" + " exceeds allowed value of 4096000"); + return 0; + } + std::set filter; - if(lua_istable(L, 3)) { - int table = 3; + if (lua_istable(L, 3)) { lua_pushnil(L); - while(lua_next(L, table) != 0) { + while (lua_next(L, 3) != 0) { // key at index -2 and value at index -1 luaL_checktype(L, -1, LUA_TSTRING); ndef->getIds(lua_tostring(L, -1), filter); // removes value, keeps key for next iteration lua_pop(L, 1); } - } else if(lua_isstring(L, 3)) { + } else if (lua_isstring(L, 3)) { ndef->getIds(lua_tostring(L, 3), filter); } - std::map individual_count; + UNORDERED_MAP individual_count; lua_newtable(L); u64 i = 0; for (s16 x = minp.X; x <= maxp.X; x++) - for (s16 y = minp.Y; y <= maxp.Y; y++) - for (s16 z = minp.Z; z <= maxp.Z; z++) { - v3s16 p(x, y, z); - content_t c = env->getMap().getNodeNoEx(p).getContent(); - if (filter.count(c) != 0) { - push_v3s16(L, p); - lua_rawseti(L, -2, ++i); - individual_count[c]++; - } + for (s16 y = minp.Y; y <= maxp.Y; y++) + for (s16 z = minp.Z; z <= maxp.Z; z++) { + v3s16 p(x, y, z); + content_t c = env->getMap().getNodeNoEx(p).getContent(); + if (filter.count(c) != 0) { + push_v3s16(L, p); + lua_rawseti(L, -2, ++i); + individual_count[c]++; + } } lua_newtable(L); - for (std::set::iterator it = filter.begin(); + for (std::set::const_iterator it = filter.begin(); it != filter.end(); ++it) { lua_pushnumber(L, individual_count[*it]); lua_setfield(L, -2, ndef->get(*it).name.c_str()); @@ -706,12 +716,21 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) INodeDefManager *ndef = getServer(L)->ndef(); v3s16 minp = read_v3s16(L, 1); v3s16 maxp = read_v3s16(L, 2); + sortBoxVerticies(minp, maxp); + + v3s16 cube = maxp - minp + 1; + // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 + if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { + luaL_error(L, "find_nodes_in_area_under_air(): area volume" + " exceeds allowed value of 4096000"); + return 0; + } + std::set filter; if (lua_istable(L, 3)) { - int table = 3; lua_pushnil(L); - while(lua_next(L, table) != 0) { + while (lua_next(L, 3) != 0) { // key at index -2 and value at index -1 luaL_checktype(L, -1, LUA_TSTRING); ndef->getIds(lua_tostring(L, -1), filter); @@ -732,7 +751,7 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) for (; y <= maxp.Y; y++) { v3s16 psurf(x, y + 1, z); content_t csurf = env->getMap().getNodeNoEx(psurf).getContent(); - if(c != CONTENT_AIR && csurf == CONTENT_AIR && + if (c != CONTENT_AIR && csurf == CONTENT_AIR && filter.count(c) != 0) { push_v3s16(L, v3s16(x, y, z)); lua_rawseti(L, -2, ++i); diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index f5e43b56..a51770f3 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -325,8 +325,8 @@ int InvRef::l_room_for_item(lua_State *L) return 1; } -// contains_item(self, listname, itemstack or itemstring or table or nil) -> true/false -// Returns true if the list contains the given count of the given item name +// contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false +// Returns true if the list contains the given count of the given item int InvRef::l_contains_item(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -334,8 +334,11 @@ int InvRef::l_contains_item(lua_State *L) const char *listname = luaL_checkstring(L, 2); ItemStack item = read_item(L, 3, getServer(L)->idef()); InventoryList *list = getlist(L, ref, listname); - if(list){ - lua_pushboolean(L, list->containsItem(item)); + bool match_meta = false; + if (lua_isboolean(L, 4)) + match_meta = lua_toboolean(L, 4); + if (list) { + lua_pushboolean(L, list->containsItem(item, match_meta)); } else { lua_pushboolean(L, false); } diff --git a/src/script/lua_api/l_inventory.h b/src/script/lua_api/l_inventory.h index 5afe3f03..1f3be1e8 100644 --- a/src/script/lua_api/l_inventory.h +++ b/src/script/lua_api/l_inventory.h @@ -93,7 +93,7 @@ private: // Returns true if the item completely fits into the list static int l_room_for_item(lua_State *L); - // contains_item(self, listname, itemstack or itemstring or table or nil) -> true/false + // contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false // Returns true if the list contains the given count of the given item name static int l_contains_item(lua_State *L); diff --git a/src/script/lua_api/l_metadata.cpp b/src/script/lua_api/l_metadata.cpp index d59f12ac..ac50893e 100644 --- a/src/script/lua_api/l_metadata.cpp +++ b/src/script/lua_api/l_metadata.cpp @@ -95,7 +95,7 @@ int MetaDataRef::l_get_int(lua_State *L) MAP_LOCK_REQUIRED; MetaDataRef *ref = checkobject(L, 1); - std::string name = lua_tostring(L, 2); + std::string name = luaL_checkstring(L, 2); Metadata *meta = ref->getmeta(false); if (meta == NULL) { @@ -114,8 +114,8 @@ int MetaDataRef::l_set_int(lua_State *L) MAP_LOCK_REQUIRED; MetaDataRef *ref = checkobject(L, 1); - std::string name = lua_tostring(L, 2); - int a = lua_tointeger(L, 3); + std::string name = luaL_checkstring(L, 2); + int a = luaL_checkint(L, 3); std::string str = itos(a); Metadata *meta = ref->getmeta(true); @@ -133,7 +133,7 @@ int MetaDataRef::l_get_float(lua_State *L) MAP_LOCK_REQUIRED; MetaDataRef *ref = checkobject(L, 1); - std::string name = lua_tostring(L, 2); + std::string name = luaL_checkstring(L, 2); Metadata *meta = ref->getmeta(false); if (meta == NULL) { @@ -152,8 +152,8 @@ int MetaDataRef::l_set_float(lua_State *L) MAP_LOCK_REQUIRED; MetaDataRef *ref = checkobject(L, 1); - std::string name = lua_tostring(L, 2); - float a = lua_tonumber(L, 3); + std::string name = luaL_checkstring(L, 2); + float a = luaL_checknumber(L, 3); std::string str = ftos(a); Metadata *meta = ref->getmeta(true); diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index ae0cd79f..34bd883c 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -158,7 +158,7 @@ bool NodeMetaRef::handleFromTable(lua_State *L, int table, Metadata *_meta) lua_pushnil(L); while (lua_next(L, inventorytable) != 0) { // key at index -2 and value at index -1 - std::string name = lua_tostring(L, -2); + std::string name = luaL_checkstring(L, -2); read_inventory_list(L, -1, inv, name.c_str(), getServer(L)); lua_pop(L, 1); // Remove value, keep key for next iteration } diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index b7be0295..250fefdc 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -316,7 +316,7 @@ int LuaPerlinNoiseMap::l_getMapSlice(lua_State *L) Noise *n = o->noise; if (use_buffer) - lua_pushvalue(L, 3); + lua_pushvalue(L, 4); else lua_newtable(L); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 1786e670..4b33c300 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_item.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "util/cpp11_container.h" #include "log.h" #include "tool.h" #include "serverobject.h" @@ -138,15 +139,15 @@ int ObjectRef::l_remove(lua_State *L) return 0; const UNORDERED_SET &child_ids = co->getAttachmentChildIds(); - UNORDERED_SET::const_iterator it; - for (it = child_ids.begin(); it != child_ids.end(); ++it) { + for (UNORDERED_SET::const_iterator it = child_ids.begin(); it != child_ids.end(); + ++it) { // Child can be NULL if it was deleted earlier if (ServerActiveObject *child = env->getActiveObject(*it)) child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); } - verbosestream<<"ObjectRef::l_remove(): id="<getId()<m_removed = true; + verbosestream << "ObjectRef::l_remove(): id=" << co->getId() << std::endl; + co->m_pending_removal = true; return 0; } diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index fb0fdd35..1e6ee52b 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -74,7 +74,7 @@ int ModApiServer::l_chat_send_all(lua_State *L) // Get server from registry Server *server = getServer(L); // Send - server->notifyPlayers(narrow_to_wide(text)); + server->notifyPlayers(utf8_to_wide(text)); return 0; } @@ -88,7 +88,7 @@ int ModApiServer::l_chat_send_player(lua_State *L) // Get server from registry Server *server = getServer(L); // Send - server->notifyPlayer(name, narrow_to_wide(text)); + server->notifyPlayer(name, utf8_to_wide(text)); return 0; } diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index ef5d48da..c553efd9 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -36,6 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/base64.h" #include "config.h" #include "version.h" +#include "util/hex.h" +#include "util/sha1.h" #include @@ -354,6 +356,23 @@ int ModApiUtil::l_get_dir_list(lua_State *L) return 1; } +// safe_file_write(path, content) +int ModApiUtil::l_safe_file_write(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *path = luaL_checkstring(L, 1); + size_t size; + const char *content = luaL_checklstring(L, 2, &size); + + CHECK_SECURE_PATH(L, path, true); + + bool ret = fs::safeWriteToFile(path, std::string(content, size)); + lua_pushboolean(L, ret); + + return 1; +} + +// request_insecure_environment() int ModApiUtil::l_request_insecure_environment(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -422,6 +441,32 @@ int ModApiUtil::l_get_version(lua_State *L) return 1; } +int ModApiUtil::l_sha1(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + size_t size; + const char *data = luaL_checklstring(L, 1, &size); + bool hex = !lua_isboolean(L, 2) || !lua_toboolean(L, 2); + + // Compute actual checksum of data + std::string data_sha1; + { + SHA1 ctx; + ctx.addBytes(data, size); + unsigned char *data_tmpdigest = ctx.getDigest(); + data_sha1.assign((char*) data_tmpdigest, 20); + free(data_tmpdigest); + } + + if (hex) { + std::string sha1_hex = hex_encode(data_sha1); + lua_pushstring(L, sha1_hex.c_str()); + } else { + lua_pushlstring(L, data_sha1.data(), data_sha1.size()); + } + + return 1; +} void ModApiUtil::Initialize(lua_State *L, int top) { @@ -447,6 +492,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(mkdir); API_FCT(get_dir_list); + API_FCT(safe_file_write); API_FCT(request_insecure_environment); @@ -454,6 +500,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(decode_base64); API_FCT(get_version); + API_FCT(sha1); LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); @@ -479,6 +526,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(decode_base64); API_FCT(get_version); + API_FCT(sha1); } void ModApiUtil::InitializeAsync(lua_State *L, int top) @@ -504,6 +552,7 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(decode_base64); API_FCT(get_version); + API_FCT(sha1); LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 7ad5af84..8737c347 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -81,6 +81,9 @@ private: // get_dir_list(path, is_dir) static int l_get_dir_list(lua_State *L); + // safe_file_write(path, content) + static int l_safe_file_write(lua_State *L); + // request_insecure_environment() static int l_request_insecure_environment(lua_State *L); @@ -93,6 +96,9 @@ private: // get_version() static int l_get_version(lua_State *L); + // sha1(string, raw) + static int l_sha1(lua_State *L); + public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); diff --git a/src/server.cpp b/src/server.cpp index 32c5b94b..97d4a1ef 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -326,7 +326,7 @@ Server::Server( Server::~Server() { - infostream<<"Server destructing"<on_shutdown(); - infostream << "Server: Saving players" << std::endl; m_env->saveLoadedPlayers(); @@ -352,6 +349,20 @@ Server::~Server() } m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, kick_msg, reconnect); + } + + // Do this before stopping the server in case mapgen callbacks need to access + // server-controlled resources (like ModStorages). Also do them before + // shutdown callbacks since they may modify state that is finalized in a + // callback. + m_emerge->stopThreads(); + + { + MutexAutoLock envlock(m_env_mutex); + + // Execute script shutdown hooks + infostream << "Executing shutdown hooks" << std::endl; + m_script->on_shutdown(); infostream << "Server: Saving environment metadata" << std::endl; m_env->saveMeta(); @@ -361,10 +372,6 @@ Server::~Server() stop(); delete m_thread; - // stop all emerge threads before deleting players that may have - // requested blocks to be emerged - m_emerge->stopThreads(); - // Delete things in the reverse order of creation delete m_emerge; delete m_env; @@ -376,7 +383,7 @@ Server::~Server() delete m_craftdef; // Deinitialize scripting - infostream<<"Server: Deinitializing scripting"< clients = m_clients.getClientIDs(); + for (std::vector::iterator it = clients.begin(); + it != clients.end(); ++it) + SendChatMessage(*it, message); } } @@ -2862,12 +2874,15 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt) } std::wstring Server::handleChat(const std::string &name, const std::wstring &wname, - const std::wstring &wmessage, bool check_shout_priv, RemotePlayer *player) + std::wstring wmessage, bool check_shout_priv, RemotePlayer *player) { // If something goes wrong, this player is to blame RollbackScopeActor rollback_scope(m_rollback, std::string("player:") + name); + if (g_settings->getBool("strip_color_codes")) + wmessage = unescape_enriched(wmessage); + if (player) { switch (player->canSendChatMessage()) { case RPLAYER_CHATRESULT_FLOODING: { @@ -2922,7 +2937,7 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna /* Send the message to others */ - actionstream << "CHAT: " << wide_to_narrow(line) << std::endl; + actionstream << "CHAT: " << wide_to_narrow(unescape_enriched(line)) << std::endl; std::vector clients = m_clients.getClientIDs(); @@ -3502,10 +3517,12 @@ v3f Server::findSpawnPos() } bool is_good = false; + // Limit spawn range to mapgen edges (determined by 'mapgen_limit') + s32 range_max = map.getMapgenParams()->getSpawnRangeMax(); // Try to find a good place a few times for(s32 i = 0; i < 4000 && !is_good; i++) { - s32 range = 1 + i; + s32 range = MYMIN(1 + i, range_max); // We're going to try to throw the player to this position v2s16 nodepos2d = v2s16( -range + (myrand() % (range * 2)), @@ -3552,7 +3569,7 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay if (delay == 0.0f) { // No delay, shutdown immediately m_shutdown_requested = true; - // only print to the infostream, a chat message saying + // only print to the infostream, a chat message saying // "Server Shutting Down" is sent when the server destructs. infostream << "*** Immediate Server shutdown requested." << std::endl; } else if (delay < 0.0f && m_shutdown_timer > 0.0f) { diff --git a/src/server.h b/src/server.h index 8f45f413..63c035d0 100644 --- a/src/server.h +++ b/src/server.h @@ -481,12 +481,13 @@ private: void RespawnPlayer(u16 peer_id); void DeleteClient(u16 peer_id, ClientDeletionReason reason); void UpdateCrafting(RemotePlayer *player); + bool checkInteractDistance(RemotePlayer *player, const f32 d, const std::string what); void handleChatInterfaceEvent(ChatEvent *evt); // This returns the answer to the sender of wmessage, or "" if there is none std::wstring handleChat(const std::string &name, const std::wstring &wname, - const std::wstring &wmessage, + std::wstring wmessage_input, bool check_shout_priv = false, RemotePlayer *player = NULL); void handleAdminChat(const ChatEventChat *evt); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 16aa0ba0..21cf0f75 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -261,16 +261,25 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp) MapNode n; content_t c; lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp); - for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) - for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) - for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) - { - n = block->getNodeNoEx(pos); - c = n.getContent(); - for (LBMManager::lbm_lookup_map::const_iterator iit = it; - iit != m_lbm_lookup.end(); ++iit) { - const std::vector *lbm_list = - iit->second.lookup(c); + for (; it != m_lbm_lookup.end(); ++it) { + // Cache previous version to speedup lookup which has a very high performance + // penalty on each call + content_t previous_c = CONTENT_IGNORE; + std::vector *lbm_list = NULL; + + for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) + for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) + for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) { + n = block->getNodeNoEx(pos); + c = n.getContent(); + + // If content_t are not matching perform an LBM lookup + if (previous_c != c) { + lbm_list = (std::vector *) + it->second.lookup(c); + previous_c = c; + } + if (!lbm_list) continue; for (std::vector::const_iterator iit = @@ -278,7 +287,7 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp) (*iit)->trigger(env, pos + pos_of_block, n); } } - } + } } /* @@ -1016,26 +1025,19 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) infostream << "ServerEnvironment::clearObjects(): " << "Removing all active objects" << std::endl; std::vector objects_to_remove; - for (ActiveObjectMap::iterator i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) { - ServerActiveObject* obj = i->second; + for (ActiveObjectMap::iterator it = m_active_objects.begin(); + it != m_active_objects.end(); ++it) { + u16 id = it->first; + ServerActiveObject* obj = it->second; if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) continue; - u16 id = i->first; + // Delete static object if block is loaded - if (obj->m_static_exists) { - MapBlock *block = m_map->getBlockNoCreateNoEx(obj->m_static_block); - if (block) { - block->m_static_objects.remove(id); - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_CLEAR_ALL_OBJECTS); - obj->m_static_exists = false; - } - } + deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true); + // If known by some client, don't delete immediately if (obj->m_known_by_count > 0) { - obj->m_pending_deactivation = true; - obj->m_removed = true; + obj->m_pending_removal = true; continue; } @@ -1052,9 +1054,9 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) } // Remove references from m_active_objects - for (std::vector::iterator i = objects_to_remove.begin(); - i != objects_to_remove.end(); ++i) { - m_active_objects.erase(*i); + for (std::vector::iterator it = objects_to_remove.begin(); + it != objects_to_remove.end(); ++it) { + m_active_objects.erase(*it); } // Get list of loaded blocks @@ -1397,16 +1399,14 @@ void ServerEnvironment::step(float dtime) for(ActiveObjectMap::iterator i = m_active_objects.begin(); i != m_active_objects.end(); ++i) { ServerActiveObject* obj = i->second; - // Don't step if is to be removed or stored statically - if(obj->m_removed || obj->m_pending_deactivation) + if (obj->isGone()) continue; + // Step object obj->step(dtime, send_recommended); // Read messages from object - while(!obj->m_messages_out.empty()) - { - m_active_object_messages.push( - obj->m_messages_out.front()); + while (!obj->m_messages_out.empty()) { + m_active_object_messages.push(obj->m_messages_out.front()); obj->m_messages_out.pop(); } } @@ -1418,9 +1418,6 @@ void ServerEnvironment::step(float dtime) if(m_object_management_interval.step(dtime, 0.5)) { ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG); - /* - Remove objects that satisfy (m_removed && m_known_by_count==0) - */ removeRemovedObjects(); } @@ -1540,7 +1537,7 @@ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius, player_radius_f = 0; /* Go through the object list, - - discard m_removed objects, + - discard removed/deactivated objects, - discard objects that are too far away, - discard objects that are found in current_objects. - add remaining objects to added_objects @@ -1554,8 +1551,7 @@ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius, if (object == NULL) continue; - // Discard if removed or deactivating - if(object->m_removed || object->m_pending_deactivation) + if (object->isGone()) continue; f32 distance_f = object->getBasePosition(). @@ -1613,7 +1609,7 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius continue; } - if (object->m_removed || object->m_pending_deactivation) { + if (object->isGone()) { removed_objects.push(id); continue; } @@ -1755,7 +1751,7 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, } /* - Remove objects that satisfy (m_removed && m_known_by_count==0) + Remove objects that satisfy (isGone() && m_known_by_count==0) */ void ServerEnvironment::removeRemovedObjects() { @@ -1764,62 +1760,54 @@ void ServerEnvironment::removeRemovedObjects() i != m_active_objects.end(); ++i) { u16 id = i->first; ServerActiveObject* obj = i->second; + // This shouldn't happen but check it - if(obj == NULL) - { - infostream<<"NULL object found in ServerEnvironment" - <<" while finding removed objects. id="<m_static_block) << std::endl; } } else { - infostream<<"Failed to emerge block from which an object to " - <<"be deactivated was loaded from. id="<getStaticData(&staticdata_new); StaticObject s_obj(obj->getType(), objectpos, staticdata_new); - block->m_static_objects.insert(id, s_obj); - obj->m_static_block = blockpos_o; - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_STATIC_DATA_ADDED); + // Save to block where object is located + saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED); - // Delete from block where object was located - block = m_map->emergeBlock(old_static_block, false); - if(!block){ - errorstream<<"ServerEnvironment::deactivateFarObjects(): " - <<"Could not delete object id="<m_static_objects.remove(id); - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_STATIC_DATA_REMOVED); continue; } - // If block is active, don't remove + // If block is still active, don't remove if(!force_delete && m_active_blocks.contains(blockpos_o)) continue; - verbosestream<<"ServerEnvironment::deactivateFarObjects(): " - <<"deactivating object id="<m_known_by_count > 0 && !force_delete); @@ -2056,7 +2010,6 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) /* Update the static data */ - if(obj->isStaticAllowed()) { // Create new static object @@ -2067,6 +2020,7 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) bool stays_in_same_block = false; bool data_changed = true; + // Check if static data has changed considerably if (obj->m_static_exists) { if (obj->m_static_block == blockpos_o) stays_in_same_block = true; @@ -2085,88 +2039,29 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) (static_old.pos - objectpos).getLength() < save_movem) data_changed = false; } else { - errorstream<<"ServerEnvironment::deactivateFarObjects(): " - <<"id="<m_static_block)<m_static_block) << std::endl; } } } + /* + While changes are always saved, blocks are only marked as modified + if the object has moved or different staticdata. (see above) + */ bool shall_be_written = (!stays_in_same_block || data_changed); + u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN; // Delete old static object - if(obj->m_static_exists) - { - MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); - if(block) - { - block->m_static_objects.remove(id); - obj->m_static_exists = false; - // Only mark block as modified if data changed considerably - if(shall_be_written) - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_STATIC_DATA_CHANGED); - } - } + deleteStaticFromBlock(obj, id, reason, false); // Add to the block where the object is located in v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS)); - // Get or generate the block - MapBlock *block = NULL; - try{ - block = m_map->emergeBlock(blockpos); - } catch(InvalidPositionException &e){ - // Handled via NULL pointer - // NOTE: emergeBlock's failure is usually determined by it - // actually returning NULL - } - - if(block) - { - if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) { - warningstream << "ServerEnv: Trying to store id = " << obj->getId() - << " statically but block " << PP(blockpos) - << " already contains " - << block->m_static_objects.m_stored.size() - << " objects." - << " Forcing delete." << std::endl; - force_delete = true; - } else { - // If static counterpart already exists in target block, - // remove it first. - // This shouldn't happen because the object is removed from - // the previous block before this according to - // obj->m_static_block, but happens rarely for some unknown - // reason. Unsuccessful attempts have been made to find - // said reason. - if(id && block->m_static_objects.m_active.find(id) != block->m_static_objects.m_active.end()){ - warningstream<<"ServerEnv: Performing hack #83274" - <m_static_objects.remove(id); - } - // Store static data - u16 store_id = pending_delete ? id : 0; - block->m_static_objects.insert(store_id, s_obj); - - // Only mark block as modified if data changed considerably - if(shall_be_written) - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_STATIC_DATA_CHANGED); - - obj->m_static_exists = true; - obj->m_static_block = block->getPos(); - } - } - else{ - if(!force_delete){ - v3s16 p = floatToInt(objectpos, BS); - errorstream<<"ServerEnv: Could not find or generate " - <<"a block for storing id="<getId() - <<" statically (pos="<m_pending_deactivation = true; continue; } - - verbosestream<<"ServerEnvironment::deactivateFarObjects(): " - <<"object id="<removingFromEnvironment(); @@ -2201,12 +2095,75 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) } // Remove references from m_active_objects - for(std::vector::iterator i = objects_to_remove.begin(); - i != objects_to_remove.end(); ++i) { - m_active_objects.erase(*i); + for (std::vector::iterator it = objects_to_remove.begin(); + it != objects_to_remove.end(); ++it) { + m_active_objects.erase(*it); } } +void ServerEnvironment::deleteStaticFromBlock( + ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge) +{ + if (!obj->m_static_exists) + return; + + MapBlock *block; + if (no_emerge) + block = m_map->getBlockNoCreateNoEx(obj->m_static_block); + else + block = m_map->emergeBlock(obj->m_static_block, false); + if (!block) { + if (!no_emerge) + errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block) + << " when deleting static data of object from it. id=" << id << std::endl; + return; + } + + block->m_static_objects.remove(id); + if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested + block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason); + + obj->m_static_exists = false; +} + +bool ServerEnvironment::saveStaticToBlock( + v3s16 blockpos, u16 store_id, + ServerActiveObject *obj, const StaticObject &s_obj, + u32 mod_reason) +{ + MapBlock *block = NULL; + try { + block = m_map->emergeBlock(blockpos); + } catch (InvalidPositionException &e) { + // Handled via NULL pointer + // NOTE: emergeBlock's failure is usually determined by it + // actually returning NULL + } + + if (!block) { + errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block) + << " when saving static data of object to it. id=" << store_id << std::endl; + return false; + } + if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) { + warningstream << "ServerEnv: Trying to store id = " << store_id + << " statically but block " << PP(blockpos) + << " already contains " + << block->m_static_objects.m_stored.size() + << " objects." << std::endl; + return false; + } + + block->m_static_objects.insert(store_id, s_obj); + if (mod_reason != MOD_REASON_UNKNOWN) // Do not mark as modified if requested + block->raiseModified(MOD_STATE_WRITE_NEEDED, mod_reason); + + obj->m_static_exists = true; + obj->m_static_block = blockpos; + + return true; +} + PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name, const std::string &savedir, const Settings &conf) { diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 83949306..99a1c593 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -33,6 +33,7 @@ class PlayerDatabase; class PlayerSAO; class ServerEnvironment; class ActiveBlockModifier; +struct StaticObject; class ServerActiveObject; class Server; class ServerScripting; @@ -362,7 +363,7 @@ private: u16 addActiveObjectRaw(ServerActiveObject *object, bool set_changed, u32 dtime_s); /* - Remove all objects that satisfy (m_removed && m_known_by_count==0) + Remove all objects that satisfy (isGone() && m_known_by_count==0) */ void removeRemovedObjects(); @@ -382,6 +383,14 @@ private: */ void deactivateFarObjects(bool force_delete); + /* + A few helpers used by the three above methods + */ + void deleteStaticFromBlock( + ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge); + bool saveStaticToBlock(v3s16 blockpos, u16 store_id, + ServerActiveObject *obj, const StaticObject &s_obj, u32 mod_reason); + /* Member variables */ diff --git a/src/serverobject.cpp b/src/serverobject.cpp index 7dce3103..9e88a84c 100644 --- a/src/serverobject.cpp +++ b/src/serverobject.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos): ActiveObject(0), m_known_by_count(0), - m_removed(false), + m_pending_removal(false), m_pending_deactivation(false), m_static_exists(false), m_static_block(1337,1337,1337), diff --git a/src/serverobject.h b/src/serverobject.h index b4761d15..4acea645 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -210,23 +210,27 @@ public: it anymore. - Removal is delayed to preserve the id for the time during which it could be confused to some other object by some client. - - This is set to true by the step() method when the object wants - to be deleted. - - This can be set to true by anything else too. + - This is usually set to true by the step() method when the object wants + to be deleted but can be set by anything else too. */ - bool m_removed; + bool m_pending_removal; /* - This is set to true when an object should be removed from the active - object list but couldn't be removed because the id has to be - reserved for some client. + Same purpose as m_pending_removal but for deactivation. + deactvation = save static data in block, remove active object - The environment checks this periodically. If this is true and also - m_known_by_count is true, object is deleted from the active object - list. + If this is set alongside with m_pending_removal, removal takes + priority. */ bool m_pending_deactivation; + /* + A getter that unifies the above to answer the question: + "Can the environment still interact with this object?" + */ + inline bool isGone() const + { return m_pending_removal || m_pending_deactivation; } + /* Whether the object's static data has been stored to a block */ diff --git a/src/settings.cpp b/src/settings.cpp index 770fcf9a..b0a42a3d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -362,6 +362,18 @@ const SettingsEntry &Settings::getEntry(const std::string &name) const } +const SettingsEntry &Settings::getEntryDefault(const std::string &name) const +{ + MutexAutoLock lock(m_mutex); + + SettingEntries::const_iterator n; + if ((n = m_defaults.find(name)) == m_defaults.end()) { + throw SettingNotFoundException("Setting [" + name + "] not found."); + } + return n->second; +} + + Settings *Settings::getGroup(const std::string &name) const { const SettingsEntry &entry = getEntry(name); @@ -380,6 +392,15 @@ const std::string &Settings::get(const std::string &name) const } +const std::string &Settings::getDefault(const std::string &name) const +{ + const SettingsEntry &entry = getEntryDefault(name); + if (entry.is_group) + throw SettingNotFoundException("Setting [" + name + "] is a group."); + return entry.value; +} + + bool Settings::getBool(const std::string &name) const { return is_yes(get(name)); @@ -568,6 +589,17 @@ bool Settings::getEntryNoEx(const std::string &name, SettingsEntry &val) const } +bool Settings::getEntryDefaultNoEx(const std::string &name, SettingsEntry &val) const +{ + try { + val = getEntryDefault(name); + return true; + } catch (SettingNotFoundException &e) { + return false; + } +} + + bool Settings::getGroupNoEx(const std::string &name, Settings *&val) const { try { @@ -590,6 +622,17 @@ bool Settings::getNoEx(const std::string &name, std::string &val) const } +bool Settings::getDefaultNoEx(const std::string &name, std::string &val) const +{ + try { + val = getDefault(name); + return true; + } catch (SettingNotFoundException &e) { + return false; + } +} + + bool Settings::getFlag(const std::string &name) const { try { diff --git a/src/settings.h b/src/settings.h index a7d4e535..62690c67 100644 --- a/src/settings.h +++ b/src/settings.h @@ -135,8 +135,10 @@ public: ***********/ const SettingsEntry &getEntry(const std::string &name) const; + const SettingsEntry &getEntryDefault(const std::string &name) const; Settings *getGroup(const std::string &name) const; const std::string &get(const std::string &name) const; + const std::string &getDefault(const std::string &name) const; bool getBool(const std::string &name) const; u16 getU16(const std::string &name) const; s16 getS16(const std::string &name) const; @@ -165,8 +167,10 @@ public: ***************************************/ bool getEntryNoEx(const std::string &name, SettingsEntry &val) const; + bool getEntryDefaultNoEx(const std::string &name, SettingsEntry &val) const; bool getGroupNoEx(const std::string &name, Settings *&val) const; bool getNoEx(const std::string &name, std::string &val) const; + bool getDefaultNoEx(const std::string &name, std::string &val) const; bool getFlag(const std::string &name) const; bool getU16NoEx(const std::string &name, u16 &val) const; bool getS16NoEx(const std::string &name, s16 &val) const; diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index 383da33a..684b125b 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -34,7 +34,7 @@ fake_function() { gettext("Random input"); gettext("Enable random user input (only used for testing)."); gettext("Continuous forward"); - gettext("Continuous forward movement (only used for testing)."); + gettext("Continuous forward movement, toggled by autoforward key."); gettext("Enable Joysticks"); gettext("Enable Joysticks"); gettext("Joystick ID"); @@ -87,8 +87,8 @@ fake_function() { gettext("Key for increasing the volume.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Dec. volume key"); gettext("Key for decreasing the volume.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Autorun key"); - gettext("Key for toggling autorun.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Autoforward key"); + gettext("Key for toggling autoforward.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Cinematic mode key"); gettext("Key for toggling cinematic mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Minimap key"); @@ -362,8 +362,8 @@ fake_function() { gettext("Automaticaly report to the serverlist."); gettext("Serverlist URL"); gettext("Announce to this serverlist.\nIf you want to announce your ipv6 address, use serverlist_url = v6.servers.minetest.net."); - gettext("Disable escape sequences"); - gettext("Disable escape sequences, e.g. chat coloring.\nUse this if you want to run a server with pre-0.4.14 clients and you want to disable\nthe escape sequences generated by mods."); + gettext("Strip color codes"); + gettext("Remove color codes from incoming chat messages\nUse this to stop players from being able to use color in their messages"); gettext("Network"); gettext("Server port"); gettext("Network port to listen (UDP).\nThis value will be overridden when starting from the main menu."); diff --git a/src/shader.cpp b/src/shader.cpp index 7768d55e..53455ffe 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -535,24 +535,19 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaderinfo.material_type = material_type; shaderinfo.drawtype = drawtype; shaderinfo.material = video::EMT_SOLID; - switch(material_type){ - case TILE_MATERIAL_BASIC: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - break; - case TILE_MATERIAL_ALPHA: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - break; - case TILE_MATERIAL_LIQUID_TRANSPARENT: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - break; - case TILE_MATERIAL_LIQUID_OPAQUE: - shaderinfo.base_material = video::EMT_SOLID; - break; - case TILE_MATERIAL_WAVING_LEAVES: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - break; - case TILE_MATERIAL_WAVING_PLANTS: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + switch (material_type) { + case TILE_MATERIAL_OPAQUE: + case TILE_MATERIAL_LIQUID_OPAQUE: + shaderinfo.base_material = video::EMT_SOLID; + break; + case TILE_MATERIAL_ALPHA: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + break; + case TILE_MATERIAL_BASIC: + case TILE_MATERIAL_WAVING_LEAVES: + case TILE_MATERIAL_WAVING_PLANTS: + shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; break; } @@ -646,10 +641,11 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp "TILE_MATERIAL_LIQUID_TRANSPARENT", "TILE_MATERIAL_LIQUID_OPAQUE", "TILE_MATERIAL_WAVING_LEAVES", - "TILE_MATERIAL_WAVING_PLANTS" + "TILE_MATERIAL_WAVING_PLANTS", + "TILE_MATERIAL_OPAQUE" }; - for (int i = 0; i < 6; i++){ + for (int i = 0; i < 7; i++){ shaders_header += "#define "; shaders_header += materialTypes[i]; shaders_header += " "; diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index 81590ade..7cbc8867 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -36,6 +36,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #include #include #endif +#include #include #include #include "log.h" @@ -331,7 +332,7 @@ public: return; } - alDistanceModel(AL_INVERSE_DISTANCE); + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); infostream<<"Audio: Initialized: OpenAL "<source_id, AL_POSITION, 0, 0, 0); alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0); alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); - volume = MYMAX(0.0, volume); + volume = MYMAX(0.0f, volume); alSourcef(sound->source_id, AL_GAIN, volume); alSourcePlay(sound->source_id); warn_if_error(alGetError(), "createPlayingSound"); @@ -428,9 +429,14 @@ public: alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false); alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z); alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0); - alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0); + // Use alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED) and set reference + // distance to clamp gain at <1 node distance, to avoid excessive + // volume when closer + alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 10.0f); alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); - volume = MYMAX(0.0, volume); + // Multiply by 3 to compensate for reducing AL_REFERENCE_DISTANCE from + // the previous value of 30 to the new value of 10 + volume = MYMAX(0.0f, volume * 3.0f); alSourcef(sound->source_id, AL_GAIN, volume); alSourcePlay(sound->source_id); warn_if_error(alGetError(), "createPlayingSoundAt"); diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index 83b0e160..0c4b0c64 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -103,8 +103,8 @@ Thread::~Thread() kill(); // Make sure start finished mutex is unlocked before it's destroyed - m_start_finished_mutex.try_lock(); - m_start_finished_mutex.unlock(); + if (m_start_finished_mutex.try_lock()) + m_start_finished_mutex.unlock(); } @@ -267,6 +267,10 @@ DWORD WINAPI Thread::threadProc(LPVOID param) thr->m_retval = thr->run(); thr->m_running = false; + // Unlock m_start_finished_mutex to prevent data race condition on Windows. + // On Windows with VS2017 build TerminateThread is called and this mutex is not + // released. We try to unlock it from caller thread and it's refused by system. + thr->m_start_finished_mutex.unlock(); g_logger.deregisterThread(); // 0 is returned here to avoid an unnecessary ifdef clause diff --git a/src/unittest/test_serialization.cpp b/src/unittest/test_serialization.cpp index 95b54b3b..f0bee97d 100644 --- a/src/unittest/test_serialization.cpp +++ b/src/unittest/test_serialization.cpp @@ -295,7 +295,7 @@ void TestSerialization::testStreamRead() UASSERT(readU8(is) == 0x11); UASSERT(readU16(is) == 0x2233); UASSERT(readU32(is) == 0x44556677); - UASSERT(readU64(is) == 0x8899AABBCCDDEEFF); + UASSERT(readU64(is) == 0x8899AABBCCDDEEFFLL); UASSERT(readS8(is) == -128); UASSERT(readS16(is) == 30000); @@ -336,7 +336,7 @@ void TestSerialization::testStreamWrite() writeU8(os, 0x11); writeU16(os, 0x2233); writeU32(os, 0x44556677); - writeU64(os, 0x8899AABBCCDDEEFF); + writeU64(os, 0x8899AABBCCDDEEFFLL); writeS8(os, -128); writeS16(os, 30000); @@ -382,7 +382,7 @@ void TestSerialization::testVecPut() putU8(&buf, 0x11); putU16(&buf, 0x2233); putU32(&buf, 0x44556677); - putU64(&buf, 0x8899AABBCCDDEEFF); + putU64(&buf, 0x8899AABBCCDDEEFFLL); putS8(&buf, -128); putS16(&buf, 30000); @@ -464,7 +464,7 @@ void TestSerialization::testBufReader() UASSERT(buf.getU8() == 0x11); UASSERT(buf.getU16() == 0x2233); UASSERT(buf.getU32() == 0x44556677); - UASSERT(buf.getU64() == 0x8899AABBCCDDEEFF); + UASSERT(buf.getU64() == 0x8899AABBCCDDEEFFLL); UASSERT(buf.getS8() == -128); UASSERT(buf.getS16() == 30000); UASSERT(buf.getS32() == -6); @@ -576,7 +576,7 @@ void TestSerialization::testBufReader() UASSERT(u8_data == 0x11); UASSERT(u16_data == 0x2233); UASSERT(u32_data == 0x44556677); - UASSERT(u64_data == 0x8899AABBCCDDEEFF); + UASSERT(u64_data == 0x8899AABBCCDDEEFFLL); UASSERT(s8_data == -128); UASSERT(s16_data == 30000); UASSERT(s32_data == -6); diff --git a/src/util/cpp11.h b/src/util/cpp11.h index 0fd45a02..5c76bc87 100644 --- a/src/util/cpp11.h +++ b/src/util/cpp11.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef MT_CPP11_HEADER #define MT_CPP11_HEADER -#if __cplusplus < 201103L || _MSC_VER < 1600 +#if __cplusplus < 201103L || (defined(_MSC_VER) && _MSC_VER < 1600) #define USE_CPP11_FAKE_KEYWORD #endif diff --git a/src/util/cpp11_container.h b/src/util/cpp11_container.h index f478c893..667841a3 100644 --- a/src/util/cpp11_container.h +++ b/src/util/cpp11_container.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define USE_UNORDERED_CONTAINERS #endif -#if _MSC_VER >= 1600 +#if defined(_MSC_VER) && _MSC_VER >= 1600 #define USE_UNORDERED_CONTAINERS #endif diff --git a/src/util/numeric.h b/src/util/numeric.h index bc1716e3..9a7fd1a8 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -254,6 +254,17 @@ inline v3s16 floatToInt(v3f p, f32 d) (p.Z + (p.Z > 0 ? d / 2 : -d / 2)) / d); } +/* + Returns integer position of node in given double precision position + */ +inline v3s16 doubleToInt(v3d p, double d) +{ + return v3s16( + (p.X + (p.X > 0 ? d / 2 : -d / 2)) / d, + (p.Y + (p.Y > 0 ? d / 2 : -d / 2)) / d, + (p.Z + (p.Z > 0 ? d / 2 : -d / 2)) / d); +} + /* Returns floating point position of node in given integer position */ diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 363e8ade..1fd15b19 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -28,8 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -SerializationError eof_ser_err("Attempted read past end of data"); - //// //// BufReader //// diff --git a/src/util/serialize.h b/src/util/serialize.h index 904a927e..831fbba2 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -429,8 +429,6 @@ bool deSerializeStringToStruct(std::string valstr, //// BufReader //// -extern SerializationError eof_ser_err; - #define MAKE_BUFREADER_GETNOEX_FXN(T, N, S) \ inline bool get ## N ## NoEx(T *val) \ { \ @@ -446,7 +444,7 @@ extern SerializationError eof_ser_err; { \ T val; \ if (!get ## N ## NoEx(&val)) \ - throw eof_ser_err; \ + throw SerializationError("Attempted read past end of data"); \ return val; \ } @@ -504,7 +502,7 @@ public: inline void getRawData(void *val, size_t len) { if (!getRawDataNoEx(val, len)) - throw eof_ser_err; + throw SerializationError("Attempted read past end of data"); } inline size_t remaining() diff --git a/src/util/srp.cpp b/src/util/srp.cpp index 4db9a746..06b6b6b4 100644 --- a/src/util/srp.cpp +++ b/src/util/srp.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include @@ -417,7 +418,7 @@ static SRP_Result H_nn( } static SRP_Result H_ns(mpz_t result, SRP_HashAlgorithm alg, const unsigned char *n, - size_t len_n, const unsigned char *bytes, size_t len_bytes) + size_t len_n, const unsigned char *bytes, uint32_t len_bytes) { unsigned char buff[SHA512_DIGEST_LENGTH]; size_t nbytes = len_n + len_bytes; diff --git a/util/travis/common.sh b/util/travis/common.sh index a5b2de72..eabce775 100644 --- a/util/travis/common.sh +++ b/util/travis/common.sh @@ -13,9 +13,9 @@ set_linux_compiler_env() { elif [[ "${COMPILER}" == "clang-3.6" ]]; then export CC=clang-3.6 export CXX=clang++-3.6 - elif [[ "${COMPILER}" == "clang-4.0" ]]; then - export CC=clang-4.0 - export CXX=clang++-4.0 + elif [[ "${COMPILER}" == "clang-5.0" ]]; then + export CC=clang-5.0 + export CXX=clang++-5.0 fi } @@ -31,7 +31,12 @@ install_linux_deps() { # Mac OSX build only install_macosx_deps() { brew update - brew install freetype gettext hiredis irrlicht jpeg leveldb libogg libvorbis luajit + brew install freetype gettext hiredis irrlicht leveldb libogg libvorbis luajit + if brew ls | grep -q jpeg; then + brew upgrade jpeg + else + brew install jpeg + fi #brew upgrade postgresql } @@ -39,6 +44,10 @@ install_macosx_deps() { TRIGGER_COMPILE_PATHS="src/.*\.(c|cpp|h)|CMakeLists.txt|cmake/Modules/|util/travis/|util/buildbot/" needs_compile() { - git diff --name-only $TRAVIS_COMMIT_RANGE | egrep -q "^($TRIGGER_COMPILE_PATHS)" + RANGE="$TRAVIS_COMMIT_RANGE" + if [[ "$(git diff --name-only $RANGE -- 2>/dev/null)" == "" ]]; then + RANGE="$TRAVIS_COMMIT^...$TRAVIS_COMMIT" + echo "Fixed range: $RANGE" + fi + git diff --name-only $RANGE -- | egrep -q "^($TRIGGER_COMPILE_PATHS)" } -