Merge remote-tracking branch 'upstream/stable-0.4' into sync

This commit is contained in:
MoNTE48 2019-04-01 20:18:54 +02:00
commit 695e833477
121 changed files with 2083 additions and 1448 deletions

3
.gitignore vendored
View File

@ -77,6 +77,7 @@ src/cmake_config_githash.h
src/lua/build/ src/lua/build/
locale/ locale/
.directory .directory
.gradle/
*.cbp *.cbp
*.layout *.layout
*.o *.o
@ -84,6 +85,8 @@ locale/
*.ninja *.ninja
.ninja* .ninja*
*.gch *.gch
*.iml
test_config.h
cmake-build-debug/ cmake-build-debug/
cmake-build-release/ cmake-build-release/

View File

@ -16,12 +16,13 @@ set(CMAKE_CXX_STANDARD 11)
set(VERSION_MAJOR 1) set(VERSION_MAJOR 1)
set(VERSION_MINOR 1) set(VERSION_MINOR 1)
set(VERSION_PATCH 10) set(VERSION_PATCH 10)
set(VERSION_TWEAK 0)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
# Change to false for releases # Change to false for releases
set(DEVELOPMENT_BUILD FALSE) 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) if(VERSION_EXTRA)
set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA}) set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA})
elseif(DEVELOPMENT_BUILD) elseif(DEVELOPMENT_BUILD)

3
build/WindowsApp/buildwin32.sh Executable file → Normal file
View File

@ -139,4 +139,7 @@ cmake .. \
make package -j $(nproc) make package -j $(nproc)
[ "x$NO_PACKAGE" = "x" ] && make package
exit 0
# EOF # EOF

3
build/WindowsApp/buildwin64.sh Executable file → Normal file
View File

@ -139,4 +139,7 @@ cmake .. \
make package -j $(nproc) make package -j $(nproc)
[ "x$NO_PACKAGE" = "x" ] && make package
exit 0
# EOF # EOF

View File

@ -17,13 +17,13 @@ allprojects {
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 27 compileSdkVersion 28
buildToolsVersion "27.0.3" buildToolsVersion "28.0.3"
defaultConfig { defaultConfig {
applicationId "mobi.MultiCraft" applicationId "mobi.MultiCraft"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 27 targetSdkVersion 28
versionCode 92 versionCode 92
} }
Properties props = new Properties() Properties props = new Properties()
@ -73,5 +73,5 @@ android.applicationVariants.all { variant ->
} }
dependencies { dependencies {
implementation 'com.android.support:support-compat:27.1.1' implementation 'com.android.support:support-compat:28.0.0'
} }

View File

@ -1,12 +1,13 @@
--- irrlicht/source/Irrlicht/CEGLManager.cpp.orig 2018-04-24 19:27:51.034727946 +0200 --- irrlicht/source/Irrlicht/CEGLManager.cpp.orig 2018-06-10 16:58:11.357709173 +0200
+++ irrlicht/source/Irrlicht/CEGLManager.cpp 2018-04-24 19:27:55.084614618 +0200 +++ irrlicht/source/Irrlicht/CEGLManager.cpp 2018-06-10 16:58:25.100709843 +0200
@@ -8,6 +8,9 @@ @@ -9,6 +9,10 @@
#include "irrString.h" #include "irrString.h"
#include "os.h" #include "os.h"
+#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) +#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
+#include <android/native_activity.h> +#include <android/native_activity.h>
+#endif +#endif
+
namespace irr namespace irr
{ {
namespace video

View File

@ -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<String> missingPermissions = new ArrayList<String>();
// 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);
}
}

View File

@ -120,7 +120,12 @@ end
-- The dumped and level arguments are internal-only. -- The dumped and level arguments are internal-only.
function dump(o, indent, nested, level) 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) return basic_dump(o)
end end
-- Contains table -> true/nil of currently nested tables -- Contains table -> true/nil of currently nested tables
@ -308,59 +313,25 @@ function core.formspec_escape(text)
end end
function core.wrap_text(text, charlimit) function core.wrap_text(text, max_length, as_table)
local retval = {} local result = {}
local line = {}
local current_idx = 1 if #text <= max_length then
return as_table and {text} or text
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 end
if last_line ~= "" then for word in text:gmatch('%S+') do
last_line = last_line .. " " 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 end
last_line = last_line .. string_sub(text, current_idx, stop - 1) table.insert(result, table.concat(line, ' '))
return as_table and result or table.concat(result, '\n')
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
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
end
return retval
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -370,7 +341,7 @@ if INIT == "game" then
local dirs2 = {20, 23, 22, 21} local dirs2 = {20, 23, 22, 21}
function core.rotate_and_place(itemstack, placer, pointed_thing, function core.rotate_and_place(itemstack, placer, pointed_thing,
infinitestacks, orient_flags) infinitestacks, orient_flags, prevent_after_place)
orient_flags = orient_flags or {} orient_flags = orient_flags or {}
local unode = core.get_node_or_nil(pointed_thing.under) local unode = core.get_node_or_nil(pointed_thing.under)
@ -379,41 +350,20 @@ if INIT == "game" then
end end
local undef = core.registered_nodes[unode.name] local undef = core.registered_nodes[unode.name]
if undef and undef.on_rightclick then 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) itemstack, pointed_thing)
return
end end
local fdir = core.dir_to_facedir(placer:get_look_dir()) local fdir = placer and core.dir_to_facedir(placer:get_look_dir()) or 0
local wield_name = itemstack:get_name()
local above = pointed_thing.above local above = pointed_thing.above
local under = pointed_thing.under local under = pointed_thing.under
local iswall = (above.y == under.y) local iswall = (above.y == under.y)
local isceiling = not iswall and (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 if undef and undef.buildable_to then
pos = pointed_thing.under
node = unode
iswall = false iswall = false
end 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 if orient_flags.force_floor then
iswall = false iswall = false
isceiling = false isceiling = false
@ -427,31 +377,26 @@ if INIT == "game" then
iswall = not iswall iswall = not iswall
end end
local param2 = fdir
if iswall then if iswall then
core.set_node(pos, {name = wield_name, param2 = dirs1[fdir + 1]
param2 = dirs1[fdir + 1]})
elseif isceiling then elseif isceiling then
if orient_flags.force_facedir then if orient_flags.force_facedir then
core.set_node(pos, {name = wield_name, cparam2 = 20
param2 = 20})
else else
core.set_node(pos, {name = wield_name, param2 = dirs2[fdir + 1]
param2 = dirs2[fdir + 1]})
end end
else -- place right side up else -- place right side up
if orient_flags.force_facedir then if orient_flags.force_facedir then
core.set_node(pos, {name = wield_name, param2 = 0
param2 = 0})
else
core.set_node(pos, {name = wield_name,
param2 = fdir})
end end
end end
if not infinitestacks then local old_itemstack = ItemStack(itemstack)
itemstack:take_item() local new_itemstack, removed = core.item_place_node(
return itemstack itemstack, placer, pointed_thing, param2, prevent_after_place
end )
return infinitestacks and old_itemstack or new_itemstack
end end
@ -459,12 +404,18 @@ if INIT == "game" then
--Wrapper for rotate_and_place() to check for sneak and assume Creative mode --Wrapper for rotate_and_place() to check for sneak and assume Creative mode
--implies infinite stacks when performing a 6d rotation. --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) 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.rotate_and_place(itemstack, placer, pointed_thing,
core.settings:get_bool("creative_mode"), is_creative(name),
{invert_wall = placer:get_player_control().sneak}) {invert_wall = invert_wall}, true)
return itemstack return itemstack
end end
end end
@ -642,32 +593,15 @@ end
local ESCAPE_CHAR = string.char(0x1b) local ESCAPE_CHAR = string.char(0x1b)
-- Client-side mods don't have access to settings function core.get_color_escape_sequence(color)
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 .. ")" return ESCAPE_CHAR .. "(c@" .. color .. ")"
end end
function core.get_background_escape_sequence(color) function core.get_background_escape_sequence(color)
return ESCAPE_CHAR .. "(b@" .. color .. ")" return ESCAPE_CHAR .. "(b@" .. color .. ")"
end end
function core.colorize(color, message) function core.colorize(color, message)
local lines = tostring(message):split("\n", true) local lines = tostring(message):split("\n", true)
local color_code = core.get_color_escape_sequence(color) local color_code = core.get_color_escape_sequence(color)
@ -676,10 +610,9 @@ else
end end
return table.concat(lines, "\n") .. core.get_color_escape_sequence("#ffffff") return table.concat(lines, "\n") .. core.get_color_escape_sequence("#ffffff")
end
end end
function core.strip_foreground_colors(str) function core.strip_foreground_colors(str)
return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", "")) return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", ""))
end end

View File

@ -67,16 +67,15 @@ local function save_auth_file()
assert(type(stuff.privileges) == "table") assert(type(stuff.privileges) == "table")
assert(stuff.last_login == nil or type(stuff.last_login) == "number") assert(stuff.last_login == nil or type(stuff.last_login) == "number")
end end
local file, errmsg = io.open(core.auth_file_path, 'w+b') local content = {}
if not file then
error(core.auth_file_path.." could not be opened for writing: "..errmsg)
end
for name, stuff in pairs(core.auth_table) do for name, stuff in pairs(core.auth_table) do
local priv_string = core.privs_to_string(stuff.privileges) local priv_string = core.privs_to_string(stuff.privileges)
local parts = {name, stuff.password, priv_string, stuff.last_login or ""} 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 end
io.close(file)
end end
read_auth_file() read_auth_file()

View File

@ -653,8 +653,8 @@ core.register_chatcommand("pulverize", {
core.rollback_punch_callbacks = {} core.rollback_punch_callbacks = {}
core.register_on_punchnode(function(pos, node, puncher) core.register_on_punchnode(function(pos, node, puncher)
local name = puncher:get_player_name() local name = puncher and puncher:get_player_name()
if core.rollback_punch_callbacks[name] then if name and core.rollback_punch_callbacks[name] then
core.rollback_punch_callbacks[name](pos, node, puncher) core.rollback_punch_callbacks[name](pos, node, puncher)
core.rollback_punch_callbacks[name] = nil core.rollback_punch_callbacks[name] = nil
end end
@ -814,7 +814,7 @@ core.register_chatcommand("shutdown", {
message = message or "" message = message or ""
if delay ~= "" then if delay ~= "" then
delay = tonumber(param) or 0 delay = tonumber(delay) or 0
else else
delay = 0 delay = 0
core.log("action", name .. " shuts down server") core.log("action", name .. " shuts down server")

View File

@ -60,8 +60,13 @@ core.register_entity(":__builtin:falling_node", {
local pos = self.object:getpos() local pos = self.object:getpos()
-- Position of bottom center point -- Position of bottom center point
local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z} 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) 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] local bcd = bcn and core.registered_nodes[bcn.name]
if bcn and if bcn and
(not bcd or bcd.walkable or (not bcd or bcd.walkable or
@ -93,7 +98,7 @@ core.register_entity(":__builtin:falling_node", {
core.remove_node(np) core.remove_node(np)
if nd and nd.buildable_to == false then if nd and nd.buildable_to == false then
-- Add dropped items -- 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 for _, dropped_item in pairs(drops) do
core.add_item(np, dropped_item) core.add_item(np, dropped_item)
end end
@ -145,9 +150,9 @@ function core.spawn_falling_node(pos)
end end
local function drop_attached_node(p) local function drop_attached_node(p)
local nn = core.get_node(p).name local n = core.get_node(p)
core.remove_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 = { local pos = {
x = p.x + math.random()/2 - 0.25, x = p.x + math.random()/2 - 0.25,
y = p.y + math.random()/2 - 0.25, y = p.y + math.random()/2 - 0.25,

View File

@ -155,11 +155,45 @@ function core.yaw_to_dir(yaw)
return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)} return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
end 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 def = core.registered_nodes[nodename]
local drop = def and def.drop 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 if drop == nil then
-- default drop -- 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} return {nodename}
elseif type(drop) == "string" then elseif type(drop) == "string" then
-- itemstring drop -- itemstring drop
@ -181,6 +215,8 @@ function core.get_node_drops(nodename, toolname)
end end
if item.tools ~= nil then if item.tools ~= nil then
good_tool = false good_tool = false
end
if item.tools ~= nil and toolname then
for _, tool in ipairs(item.tools) do for _, tool in ipairs(item.tools) do
if tool:sub(1, 1) == '~' then if tool:sub(1, 1) == '~' then
good_tool = toolname:find(tool:sub(2)) ~= nil good_tool = toolname:find(tool:sub(2)) ~= nil
@ -195,6 +231,12 @@ function core.get_node_drops(nodename, toolname)
if good_rarity and good_tool then if good_rarity and good_tool then
got_count = got_count + 1 got_count = got_count + 1
for _, add_item in ipairs(item.items) do 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 got_items[#got_items+1] = add_item
end end
if drop.max_items ~= nil and got_count == drop.max_items then 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 return got_items
end 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() local def = itemstack:get_definition()
if def.type ~= "node" or pointed_thing.type ~= "node" then if def.type ~= "node" or pointed_thing.type ~= "node" then
return itemstack, false 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 oldnode_under = core.get_node_or_nil(under)
local above = pointed_thing.above local above = pointed_thing.above
local oldnode_above = core.get_node_or_nil(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 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)) .. " node in unloaded position " .. core.pos_to_string(above))
return itemstack, false return itemstack, false
end end
@ -229,7 +287,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2)
olddef_above = olddef_above or core.nodedef_default olddef_above = olddef_above or core.nodedef_default
if not olddef_above.buildable_to and not olddef_under.buildable_to then 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) .. " node in invalid position " .. core.pos_to_string(above)
.. ", replacing " .. oldnode_above.name) .. ", replacing " .. oldnode_above.name)
return itemstack, false 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 node under is buildable_to, place into it instead (eg. snow)
if olddef_under.buildable_to then 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} place_to = {x = under.x, y = under.y, z = under.z}
end end
if core.is_protected(place_to, playername) and if is_protected(place_to, playername) then
not minetest.check_player_privs(placer, "protection_bypass") then log("action", playername
core.log("action", playername
.. " tried to place " .. def.name .. " tried to place " .. def.name
.. " at protected position " .. " at protected position "
.. core.pos_to_string(place_to)) .. core.pos_to_string(place_to))
@ -254,11 +311,11 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2)
return itemstack return itemstack
end end
core.log("action", playername .. " places node " log("action", playername .. " places node "
.. def.name .. " at " .. core.pos_to_string(place_to)) .. def.name .. " at " .. core.pos_to_string(place_to))
local oldnode = core.get_node(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 -- Calculate direction for wall mounted stuff like torches and signs
if def.place_param2 ~= nil then 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 -- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir") and not param2 then def.paramtype2 == "colorfacedir") and not param2 then
local placer_pos = placer:getpos() local placer_pos = placer and placer:getpos()
if placer_pos then if placer_pos then
local dir = { local dir = {
x = above.x - placer_pos.x, 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 z = above.z - placer_pos.z
} }
newnode.param2 = core.dir_to_facedir(dir) 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
end end
-- Check if the node is attached and if it can be placed there -- Check if the node is attached and if it can be placed there
if core.get_item_group(def.name, "attached_node") ~= 0 and if core.get_item_group(def.name, "attached_node") ~= 0 and
not builtin_shared.check_attached_node(place_to, newnode) then 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)) " can not be placed at " .. core.pos_to_string(place_to))
return itemstack, false return itemstack, false
end end
@ -300,7 +376,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2)
local take_item = true local take_item = true
-- Run callback -- 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 -- Deepcopy place_to and pointed_thing because callback can modify it
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
local pointed_thing_copy = copy_pointed_thing(pointed_thing) local pointed_thing_copy = copy_pointed_thing(pointed_thing)
@ -360,29 +436,28 @@ function core.item_secondary_use(itemstack, placer)
end end
function core.item_drop(itemstack, dropper, pos) function core.item_drop(itemstack, dropper, pos)
if dropper and dropper:is_player() then local dropper_is_player = dropper and dropper:is_player()
local v = dropper:get_look_dir() local p = table.copy(pos)
local p = {x=pos.x, y=pos.y+1.2, z=pos.z} local cnt = itemstack:get_count()
local cs = itemstack:get_count() if dropper_is_player then
p.y = p.y + 1.2
if dropper:get_player_control().sneak then if dropper:get_player_control().sneak then
cs = 1 cnt = 1
end end
local item = itemstack:take_item(cs) end
local item = itemstack:take_item(cnt)
local obj = core.add_item(p, item) local obj = core.add_item(p, item)
if obj then if obj then
v.x = v.x*2 if dropper_is_player then
v.y = v.y*2 + 2 local dir = dropper:get_look_dir()
v.z = v.z*2 dir.x = dir.x * 2.9
obj:setvelocity(v) 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() obj:get_luaentity().dropped_by = dropper:get_player_name()
return itemstack
end end
else
if core.add_item(pos, itemstack) then
return itemstack return itemstack
end end
end
-- If we reach this, adding the object to the -- If we reach this, adding the object to the
-- environment failed -- environment failed
end end
@ -402,7 +477,8 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
itemstack:add_item(replace_with_item) itemstack:add_item(replace_with_item)
else else
local inv = user:get_inventory() 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) inv:add_item("main", replace_with_item)
else else
local pos = user:getpos() local pos = user:getpos()
@ -417,8 +493,10 @@ end
function core.item_eat(hp_change, replace_with_item) function core.item_eat(hp_change, replace_with_item)
return function(itemstack, user, pointed_thing) -- closure return function(itemstack, user, pointed_thing) -- closure
if user then
return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing) return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
end end
end
end end
function core.node_punch(pos, node, puncher, pointed_thing) function core.node_punch(pos, node, puncher, pointed_thing)
@ -434,10 +512,21 @@ end
function core.handle_node_drops(pos, drops, digger) function core.handle_node_drops(pos, drops, digger)
-- Add dropped items to object's inventory -- Add dropped items to object's inventory
if digger:get_inventory() then local inv = digger and digger:get_inventory()
local _, dropped_item local give_item
for _, dropped_item in ipairs(drops) do if inv then
local left = digger:get_inventory():add_item("main", dropped_item) 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 if not left:is_empty() then
local p = { local p = {
x = pos.x + math.random()/2-0.25, x = pos.x + math.random()/2-0.25,
@ -447,35 +536,36 @@ function core.handle_node_drops(pos, drops, digger)
core.add_item(p, left) core.add_item(p, left)
end end
end end
end
end end
function core.node_dig(pos, node, digger) function core.node_dig(pos, node, digger)
local diggername = user_name(digger)
local log = make_log(diggername)
local def = core.registered_nodes[node.name] local def = core.registered_nodes[node.name]
if def and (not def.diggable or if def and (not def.diggable or
(def.can_dig and not def.can_dig(pos, digger))) then (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 " .. node.name .. " which is not diggable "
.. core.pos_to_string(pos)) .. core.pos_to_string(pos))
return return
end end
if core.is_protected(pos, digger:get_player_name()) and if is_protected(pos, diggername) then
not minetest.check_player_privs(digger, "protection_bypass") then log("action", diggername
core.log("action", digger:get_player_name()
.. " tried to dig " .. node.name .. " tried to dig " .. node.name
.. " at protected position " .. " at protected position "
.. core.pos_to_string(pos)) .. core.pos_to_string(pos))
core.record_protection_violation(pos, digger:get_player_name()) core.record_protection_violation(pos, diggername)
return return
end end
core.log('action', digger:get_player_name() .. " digs " log('action', diggername .. " digs "
.. node.name .. " at " .. core.pos_to_string(pos)) .. node.name .. " at " .. core.pos_to_string(pos))
local wielded = digger:get_wielded_item() local wielded = digger and digger:get_wielded_item()
local drops = core.get_node_drops(node.name, wielded:get_name()) local drops = core.get_node_drops(node, wielded and wielded:get_name())
if wielded then
local wdef = wielded:get_definition() local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities() local tp = wielded:get_tool_capabilities()
local dp = core.get_dig_params(def and def.groups, tp) local dp = core.get_dig_params(def and def.groups, tp)
@ -491,6 +581,7 @@ function core.node_dig(pos, node, digger)
end end
end end
digger:set_wielded_item(wielded) digger:set_wielded_item(wielded)
end
-- Handle drops -- Handle drops
core.handle_node_drops(pos, drops, digger) core.handle_node_drops(pos, drops, digger)

View File

@ -174,19 +174,18 @@ core.register_entity(":__builtin:item", {
local p = self.object:getpos() local p = self.object:getpos()
p.y = p.y - 0.5 p.y = p.y - 0.5
local node = core.get_node_or_nil(p) local node = core.get_node_or_nil(p)
local in_unloaded = (node == nil) -- Delete in 'ignore' nodes
if in_unloaded then if node and node.name == "ignore" then
-- Don't infinetly fall into unloaded map self.itemstring = ""
self.object:setvelocity({x = 0, y = 0, z = 0}) self.object:remove()
self.object:setacceleration({x = 0, y = 0, z = 0})
self.physical_state = false
self.object:set_properties({physical = false})
return return
end 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() 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 if self.physical_state then
local own_stack = ItemStack(self.object:get_luaentity().itemstring) local own_stack = ItemStack(self.object:get_luaentity().itemstring)
-- Merge with close entities of the same item -- Merge with close entities of the same item

View File

@ -5,12 +5,11 @@
-- --
function core.check_player_privs(name, ...) function core.check_player_privs(name, ...)
local arg_type = type(name) if core.is_player(name) then
if (arg_type == "userdata" or arg_type == "table") and
name.get_player_name then -- If it quacks like a Player...
name = name:get_player_name() name = name:get_player_name()
elseif arg_type ~= "string" then elseif type(name) ~= "string" then
error("Invalid core.check_player_privs argument type: " .. arg_type, 2) error("core.check_player_privs expects a player or playername as " ..
"argument.", 2)
end end
local requested_privs = {...} local requested_privs = {...}
@ -70,6 +69,16 @@ function core.get_connected_players()
return temp_table return temp_table
end 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) function minetest.player_exists(name)
return minetest.get_auth_handler().get_auth(name) ~= nil return minetest.get_auth_handler().get_auth(name) ~= nil
end end

View File

@ -116,6 +116,8 @@ function core.register_item(name, itemdef)
end end
itemdef.name = name itemdef.name = name
local is_overriding = core.registered_items[name]
-- Apply defaults and add to registered_* table -- Apply defaults and add to registered_* table
if itemdef.type == "node" then if itemdef.type == "node" then
-- Use the nodebox as selection box if it's not set manually -- 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.log("Registering item: " .. itemdef.name)
core.registered_items[itemdef.name] = itemdef core.registered_items[itemdef.name] = itemdef
core.registered_aliases[itemdef.name] = nil core.registered_aliases[itemdef.name] = nil
-- Used to allow builtin to register ignore to registered_items
if name ~= "ignore" then
register_item_raw(itemdef) register_item_raw(itemdef)
elseif is_overriding then
core.log("warning", "Attempted redefinition of \"ignore\"")
end
end end
function core.unregister_item(name) function core.unregister_item(name)

View File

@ -21,7 +21,6 @@ if core.print then
core.print = nil -- don't pollute our namespace core.print = nil -- don't pollute our namespace
end end
math.randomseed(os.time()) math.randomseed(os.time())
os.setlocale("C", "numeric")
minetest = core minetest = core
-- Load other files -- Load other files
@ -47,7 +46,6 @@ elseif INIT == "mainmenu" then
elseif INIT == "async" then elseif INIT == "async" then
dofile(asyncpath .. "init.lua") dofile(asyncpath .. "init.lua")
elseif INIT == "client" then elseif INIT == "client" then
os.setlocale = nil
dofile(clientpath .. "init.lua") dofile(clientpath .. "init.lua")
else else
error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))

View File

@ -249,7 +249,7 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transparency) 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 .. local retval = "textlist[" .. xpos .. "," .. ypos .. ";" .. width ..
"," .. height .. ";" .. tl_name .. ";" "," .. height .. ";" .. tl_name .. ";"

View File

@ -75,7 +75,7 @@ local function get_formspec(tabview, name, tabdata)
if error == nil then if error == nil then
local descriptiontext = descriptionfile:read("*all") local descriptiontext = descriptionfile:read("*all")
descriptionlines = core.wrap_text(descriptiontext, 42) descriptionlines = core.wrap_text(descriptiontext, 42, true)
descriptionfile:close() descriptionfile:close()
else else
descriptionlines = {} descriptionlines = {}

View File

@ -124,7 +124,7 @@ end
local function formspec(tabview, name, tabdata) local function formspec(tabview, name, tabdata)
local tab_string = 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") .. ";" "checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") .. ";"
.. dump(core.settings:get_bool("smooth_lighting")) .. "]" .. .. dump(core.settings:get_bool("smooth_lighting")) .. "]" ..
"checkbox[0.25,0.5;cb_particles;" .. fgettext("Particles") .. ";" "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")) .. "]" .. .. dump(core.settings:get_bool("opaque_water")) .. "]" ..
"checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";" "checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";"
.. dump(core.settings:get_bool("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() .. "]" .. .. 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() .. "]" .. .. getSettingIndex.Leaves() .. "]" ..
"box[3.75,0;3.75,4.45;#999999]" .. "box[4,0;3.75,4.5;#999999]" ..
"label[3.85,0.1;" .. fgettext("Texturing:") .. "]" .. "label[4.25,0.1;" .. fgettext("Texturing:") .. "]" ..
"dropdown[3.85,0.55;3.85;dd_filters;" .. dd_options.filters[1] .. ";" "dropdown[4.25,0.55;3.5;dd_filters;" .. dd_options.filters[1] .. ";"
.. getSettingIndex.Filter() .. "]" .. .. 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() .. "]" .. .. getSettingIndex.Mipmap() .. "]" ..
"label[3.85,2.15;" .. fgettext("Antialiasing:") .. "]" .. "label[4.25,2.15;" .. fgettext("Antialiasing:") .. "]" ..
"dropdown[3.85,2.6;3.85;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";" "dropdown[4.25,2.6;3.5;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";"
.. getSettingIndex.Antialiasing() .. "]" .. .. getSettingIndex.Antialiasing() .. "]" ..
"label[3.85,3.45;" .. fgettext("Screen:") .. "]" .. "label[4.25,3.45;" .. fgettext("Screen:") .. "]" ..
"checkbox[3.85,3.6;cb_autosave_screensize;" .. fgettext("Autosave screen size") .. ";" "checkbox[4.25,3.6;cb_autosave_screensize;" .. fgettext("Autosave screen size") .. ";"
.. dump(core.settings:get_bool("autosave_screensize")) .. "]" .. .. dump(core.settings:get_bool("autosave_screensize")) .. "]" ..
"box[7.75,0;4,4.4;#999999]" .. "box[8,0;3.75,4.5;#999999]" ..
"checkbox[8,0;cb_shaders;" .. fgettext("Shaders") .. ";" "checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders") .. ";"
.. dump(core.settings:get_bool("enable_shaders")) .. "]" .. dump(core.settings:get_bool("enable_shaders")) .. "]"
if PLATFORM ~= "Android" or PLATFORM ~= "iOS" then if PLATFORM ~= "Android" or PLATFORM ~= "iOS" then
tab_string = tab_string .. 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") .. "]" .. fgettext("Change keys") .. "]"
end end
tab_string = tab_string .. 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") .. "]" .. fgettext("Advanced Settings") .. "]"
@ -175,19 +175,19 @@ local function formspec(tabview, name, tabdata)
if core.settings:get_bool("enable_shaders") then if core.settings:get_bool("enable_shaders") then
tab_string = tab_string .. 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")) .. "]" .. .. 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")) .. "]" .. .. 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")) .. "]" .. .. 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")) .. "]" .. .. 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")) .. "]" .. .. 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")) .. "]" .. .. 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")) .. "]" .. dump(core.settings:get_bool("enable_waving_plants")) .. "]"
else else
tab_string = tab_string .. tab_string = tab_string ..

View File

@ -133,7 +133,7 @@ local function instrument_register(func, func_name)
return func(instrument { return func(instrument {
func = callback, func = callback,
func_name = register_name func_name = register_name
}), ... }, ...)
end end
end end

View File

@ -166,10 +166,6 @@ keymap_cmd (Command key) key /
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_cmd_local (Command key) key . 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. # Key for toggling unlimited view range.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_rangeselect (Range select key) key KEY_KEY_R 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. # View distance in nodes.
viewing_range (Viewing range) int 100 20 4000 viewing_range (Viewing range) int 100 20 4000
# Far view distance in nodes. # Camera near plane distance in nodes, between 0 and 0.5
# Only for Android. # Most users will not need to change this.
viewing_range_secondary (Initial secondary viewing range) int 150 # 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. # Width component of the initial window size.
screenW (Screen width) int 800 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. # If you want to announce your ipv6 address, use serverlist_url = v6.servers.minetest.net.
serverlist_url (Serverlist URL) string servers.minetest.net serverlist_url (Serverlist URL) string servers.minetest.net
# Disable escape sequences, e.g. chat coloring. # Remove color codes from incoming chat messages
# Use this if you want to run a server with pre-0.4.14 clients and you want to disable # Use this to stop players from being able to use color in their messages
# the escape sequences generated by mods. strip_color_codes (Strip color codes) bool false
disable_escape_sequences (Disable escape sequences) bool false
[*Network] [*Network]

View File

@ -9,7 +9,7 @@
FIND_PATH(LUA_INCLUDE_DIR luajit.h FIND_PATH(LUA_INCLUDE_DIR luajit.h
HINTS HINTS
$ENV{LUA_DIR} $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 PATHS
~/Library/Frameworks ~/Library/Frameworks
/Library/Frameworks /Library/Frameworks

View File

@ -628,6 +628,9 @@ Minetest namespace reference
version entirely. To check for the presence of engine features, test version entirely. To check for the presence of engine features, test
whether the functions exported by the wanted features exist. For example: whether the functions exported by the wanted features exist. For example:
`if minetest.nodeupdate then ... end`. `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 ### Logging
* `minetest.debug(...)` * `minetest.debug(...)`
@ -1117,15 +1120,15 @@ The following functions provide escape sequences:
`minetest.get_color_escape_sequence(color) .. `minetest.get_color_escape_sequence(color) ..
message .. message ..
minetest.get_color_escape_sequence("#ffffff")` minetest.get_color_escape_sequence("#ffffff")`
* `color.get_background_escape_sequence(color)` * `minetest.get_background_escape_sequence(color)`
* `color` is a [ColorString](#colorstring) * `color` is a [ColorString](#colorstring)
* The escape sequence sets the background of the whole text element to * The escape sequence sets the background of the whole text element to
`color`. Only defined for item descriptions and tooltips. `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`. * 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`. * Removes background colors added by `get_background_escape_sequence`.
* `color.strip_colors(str)` * `minetest.strip_colors(str)`
* Removes all color escape sequences. * Removes all color escape sequences.
`ColorString` `ColorString`

View File

@ -1,4 +1,4 @@
Minetest Lua Modding API Reference 0.4.16 Minetest Lua Modding API Reference 0.4.17
========================================= =========================================
* More information at <http://www.minetest.net/> * More information at <http://www.minetest.net/>
* Developer Wiki: <http://dev.minetest.net/> * Developer Wiki: <http://dev.minetest.net/>
@ -211,7 +211,8 @@ when registering it.
The `:` prefix can also be used for maintaining backwards compatibility. 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 Aliases can be added by using `minetest.register_alias(name, convert_to)` or
`minetest.register_alias_force(name, convert_to)`. `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`. 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 Textures
-------- --------
Mods should generally prefix their textures with `modname_`, e.g. given 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 If the `ItemStack`'s metadata contains the `color` field, it will be
lost on placement, because nodes on the map can only use palettes. lost on placement, because nodes on the map can only use palettes.
If the `ItemStack`'s metadata contains the `palette_index` field, you If the `ItemStack`'s metadata contains the `palette_index` field, it is
currently must manually convert between it and the node's `param2` with automatically transferred between node and item forms by the engine,
custom `on_place` and `on_dig` callbacks. 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 ### Colored items in craft recipes
Craft recipes only support item strings, but fortunately item strings Craft recipes only support item strings, but fortunately item strings
@ -792,6 +879,11 @@ node definition:
0 = y+ 1 = z+ 2 = z- 3 = x+ 4 = x- 5 = y- 0 = y+ 1 = z+ 2 = z- 3 = x+ 4 = x- 5 = y-
facedir modulo 4 = rotation around that axis facedir modulo 4 = rotation around that axis
paramtype2 == "leveled" 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" paramtype2 == "degrotate"
^ The rotation of this node is stored in param2. Plants are rotated this way. ^ 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 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) .. `minetest.get_color_escape_sequence(color) ..
message .. message ..
minetest.get_color_escape_sequence("#ffffff")` minetest.get_color_escape_sequence("#ffffff")`
* `color.get_background_escape_sequence(color)` * `minetest.get_background_escape_sequence(color)`
* `color` is a ColorString * `color` is a ColorString
* The escape sequence sets the background of the whole text element to * The escape sequence sets the background of the whole text element to
`color`. Only defined for item descriptions and tooltips. `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`. * 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`. * Removes background colors added by `get_background_escape_sequence`.
* `color.strip_colors(str)` * `minetest.strip_colors(str)`
* Removes all color escape sequences. * Removes all color escape sequences.
Spatial Vectors Spatial Vectors
@ -2111,9 +2203,11 @@ Helper functions
* e.g. `string:split("a,b", ",") == {"a","b"}` * e.g. `string:split("a,b", ",") == {"a","b"}`
* `string:trim()` * `string:trim()`
* e.g. `string.trim("\n \t\tfoo bar\t ") == "foo bar"` * e.g. `string.trim("\n \t\tfoo bar\t ") == "foo bar"`
* `minetest.wrap_text(str, limit)`: returns a string * `minetest.wrap_text(str, limit, [as_table])`: returns a string or table
* Adds new lines to the string to keep it within the specified character limit * 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 * 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)"` * `minetest.pos_to_string({x=X,y=Y,z=Z}, decimal_places))`: returns string `"(X,Y,Z)"`
* Convert position to a printable string * Convert position to a printable string
Optional: 'decimal_places' will round the x, y and z of the pos to the given decimal place. 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 max_jitter = 0.5, -- maximum packet time jitter
avg_jitter = 0.03, -- average packet time jitter avg_jitter = 0.03, -- average packet time jitter
connection_uptime = 200, -- seconds since client connected 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!!! -- following information is available on debug build only!!!
-- DO NOT USE IN MODS -- DO NOT USE IN MODS
--ser_vers = 26, -- serialization version used by client --ser_vers = 26, -- serialization version used by client
@ -2199,6 +2293,10 @@ Helper functions
* nil: return all entries, * nil: return all entries,
* true: return only subdirectory names, or * true: return only subdirectory names, or
* false: return only file names. * 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 * `minetest.get_version()`: returns a table containing components of the
engine version. Components: engine version. Components:
* `project`: Name of the project, eg, "Minetest" * `project`: Name of the project, eg, "Minetest"
@ -2210,6 +2308,9 @@ Helper functions
version entirely. To check for the presence of engine features, test version entirely. To check for the presence of engine features, test
whether the functions exported by the wanted features exist. For example: whether the functions exported by the wanted features exist. For example:
`if minetest.nodeupdate then ... end`. `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 ### Logging
* `minetest.debug(...)` * `minetest.debug(...)`
@ -2229,6 +2330,8 @@ Call these functions only at load time!
* `minetest.register_craftitem(name, item definition)` * `minetest.register_craftitem(name, item definition)`
* `minetest.unregister_item(name)` * `minetest.unregister_item(name)`
* `minetest.register_alias(name, convert_to)` * `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_alias_force(name, convert_to)`
* `minetest.register_craft(recipe)` * `minetest.register_craft(recipe)`
* Check recipe table syntax for different types below. * 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))` * `minetest.register_on_placenode(func(pos, newnode, placer, oldnode, itemstack, pointed_thing))`
* Called when a node has been placed * Called when a node has been placed
* If return `true` no item is taken from `itemstack` * 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 * **Not recommended**; use `on_construct` or `after_place_node` in node definition
whenever possible whenever possible
* `minetest.register_on_dignode(func(pos, oldnode, digger))` * `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}` * `definition`: `{ description = "description text", give_to_singleplayer = boolean}`
the default of `give_to_singleplayer` is true the default of `give_to_singleplayer` is true
* To allow players with `basic_privs` to grant, see `basic_privs` minetest.conf setting. * To allow players with `basic_privs` to grant, see `basic_privs` minetest.conf setting.
* `minetest.register_authentication_handler(handler)` * `minetest.register_authentication_handler(authentication handler definition)`
* See `minetest.builtin_auth_handler` in `builtin.lua` for reference * Registers an auth handler that overrides the builtin one
* This function can be called by a single mod once only.
### Setting-related ### Setting-related
* `minetest.settings`: Settings object containing all of the settings from the * `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. parses it as a position (in the format `(1,2,3)`). Returns a position or nil.
### Authentication ### 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.string_to_privs(str)`: returns `{priv1=true,...}`
* `minetest.privs_to_string(privs)`: returns `"priv1,priv2,..."` * `minetest.privs_to_string(privs)`: returns `"priv1,priv2,..."`
* Convert between two privilege representations * 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.get_player_privs(name) -> {priv1=true,...}`
* `minetest.auth_reload()`
* `minetest.check_player_privs(player_or_name, ...)`: returns `bool, missing_privs` * `minetest.check_player_privs(player_or_name, ...)`: returns `bool, missing_privs`
* A quickhand for checking privileges. * A quickhand for checking privileges.
* `player_or_name`: Either a Player object or the name of a player. * `player_or_name`: Either a Player object or the name of a player.
* `...` is either a list of strings, e.g. `"priva", "privb"` or * `...` is either a list of strings, e.g. `"priva", "privb"` or
a table, e.g. `{ priva = true, privb = true }`. a table, e.g. `{ priva = true, privb = true }`.
* `minetest.get_player_ip(name)`: returns an IP address string
* `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` `minetest.set_player_password`, `minetest_set_player_privs`, `minetest_get_player_privs`
and `minetest.auth_reload` call the authetification handler. 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"` * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
* `search_center` is an optional boolean (default: `false`) * `search_center` is an optional boolean (default: `false`)
If true `pos` is also checked for the nodes If true `pos` is also checked for the nodes
* `minetest.find_nodes_in_area(minp, maxp, nodenames)`: returns a list of positions * `minetest.find_nodes_in_area(pos1, pos2, nodenames)`: returns a list of positions
* returns as second value a table with the count of the individual nodes found
* `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
* `minetest.find_nodes_in_area_under_air(minp, maxp, nodenames)`: returns a list of positions * First return value: Table with all node positions
* returned positions are nodes with a node air above * 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"` * `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(noiseparams)`
* `minetest.get_perlin(seeddiff, octaves, persistence, scale)` * `minetest.get_perlin(seeddiff, octaves, persistence, scale)`
* Return world-specific perlin noise (`int(worldseed)+seeddiff`) * 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) * Convert a vector into a yaw (angle)
* `minetest.yaw_to_dir(yaw)` * `minetest.yaw_to_dir(yaw)`
* Convert yaw (angle) to a vector * 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)` * `minetest.get_node_drops(nodename, toolname)`
* Returns list of item names. * Returns list of item names.
* **Note**: This will be removed or modified in a future version. * **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: * 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"}}, 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"}} items = {1 = "default:goldblock"}}
} }
* `minetest.handle_node_drops(pos, drops, digger)` * `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 ### Defaults for the `on_*` item definition functions
These functions return the leftover itemstack. 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 * Place item as a node
* `param2` overrides `facedir` and wallmounted `param2` * `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` * returns `itemstack, success`
* `minetest.item_place_object(itemstack, placer, pointed_thing)` * `minetest.item_place_object(itemstack, placer, pointed_thing)`
* Place item as-is * Place item as-is
@ -2893,6 +3017,7 @@ These functions return the leftover itemstack.
### Misc. ### Misc.
* `minetest.get_connected_players()`: returns list of `ObjectRefs` * `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.player_exists(name)`: boolean, whether player exists (regardless of online status)
* `minetest.hud_replace_builtin(name, hud_definition)` * `minetest.hud_replace_builtin(name, hud_definition)`
* Replaces definition of a builtin hud element * 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 * 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. 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. 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 * This function should be overridden by protection mods and should be used to
check if a player can interact at a position. check if a player can interact at a position.
* This function should call the old version of itself if the position is not * 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)` * `minetest.record_protection_violation(pos, name)`
* This function calls functions registered with * This function calls functions registered with
`minetest.register_on_protection_violation`. `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 * Attempt to predict the desired orientation of the facedir-capable node
defined by `itemstack`, and place it accordingly (on-wall, on the floor, or defined by `itemstack`, and place it accordingly (on-wall, on the floor,
hanging from the ceiling). Stacks are handled normally if the `infinitestacks` or hanging from the ceiling).
field is false or omitted (else, the itemstack is not changed). `orient_flags` * `infinitestacks`: if `true`, the itemstack is not changed. Otherwise the
is an optional table containing extra tweaks to the placement code: stacks are handled normally.
* `invert_wall`: if `true`, place wall-orientation on the ground and ground- * `orient_flags`: Optional table containing extra tweaks to the placement code:
orientation on the wall. * `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_wall` : if `true`, always place the node in wall orientation.
* `force_ceiling`: if `true`, always place on the ceiling. * `force_ceiling`: if `true`, always place on the ceiling.
* `force_floor`: if `true`, always place the node on the floor. * `force_floor`: if `true`, always place the node on the floor.
* `force_facedir`: if `true`, forcefully reset the facedir to north when placing on * `force_facedir`: if `true`, forcefully reset the facedir to north
the floor or ceiling when placing on the floor or ceiling.
* The first four options are mutually-exclusive; the last in the list takes * The first four options are mutually-exclusive; the last in the list
precedence over the first. 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)` * `minetest.rotate_node(itemstack, placer, pointed_thing)`
* calls `rotate_and_place()` with infinitestacks set according to the state of * calls `rotate_and_place()` with `infinitestacks` set according to the state
the creative mode setting, and checks for "sneak" to set the `invert_wall` of the creative mode setting, checks for "sneak" to set the `invert_wall`
parameter. parameter and `prevent_after_place` set to `true`.
* `minetest.forceload_block(pos[, transient])` * `minetest.forceload_block(pos[, transient])`
* forceloads the position `pos`. * forceloads the position `pos`.
@ -3150,7 +3280,7 @@ This is basically a reference to a C++ `ServerActiveObject`
* `set_attach(parent, bone, position, rotation)` * `set_attach(parent, bone, position, rotation)`
* `bone`: string * `bone`: string
* `position`: `{x=num, y=num, z=num}` (relative) * `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 * `get_attach()`: returns parent, bone, position, rotation or nil if it isn't attached
* `set_detach()` * `set_detach()`
* `set_bone_position(bone, position, rotation)` * `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 * `11`: bubbles bar is not shown
* `set_attribute(attribute, value)`: * `set_attribute(attribute, value)`:
* Sets an extra attribute with value on player. * 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. * If `value` is `nil`, remove attribute from player.
* `get_attribute(attribute)`: * `get_attribute(attribute)`:
* Returns value (a string) for extra 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` * `add_item(listname, stack)`: add item somewhere in list, returns leftover `ItemStack`
* `room_for_item(listname, stack):` returns `true` if the stack of items * `room_for_item(listname, stack):` returns `true` if the stack of items
can be fully added to the list can be fully added to the list
* `contains_item(listname, stack)`: returns `true` if the stack of items * `contains_item(listname, stack, [match_meta])`: returns `true` if
can be fully taken from the list 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, * `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 returns the items that were actually removed (as an `ItemStack`) -- note that
any item metadata is ignored, so attempting to remove a specific unique 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. items = {"foo:bar", "baz:frob"}, -- Items to drop.
rarity = 1, -- Probability of dropping is 1 / rarity. 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 ^ Called after constructing node when node was placed using
minetest.item_place_node / minetest.place_node minetest.item_place_node / minetest.place_node
^ If return true no item is taken from itemstack ^ If return true no item is taken from itemstack
^ `placer` may be any valid ObjectRef or nil
^ default: nil ]] ^ default: nil ]]
after_dig_node = func(pos, oldnode, oldmetadata, digger), --[[ after_dig_node = func(pos, oldnode, oldmetadata, digger), --[[
^ oldmetadata is in table format ^ oldmetadata is in table format
@ -4257,9 +4390,11 @@ Definition tables
^ By default: Calls minetest.register_on_punchnode callbacks ]] ^ By default: Calls minetest.register_on_punchnode callbacks ]]
on_rightclick = func(pos, node, clicker, itemstack, pointed_thing), --[[ on_rightclick = func(pos, node, clicker, itemstack, pointed_thing), --[[
^ default: nil ^ default: nil
^ if defined, itemstack will hold clicker's wielded item ^ itemstack will hold clicker's wielded item
^ Shall return the leftover itemstack ^ 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), --[[ on_dig = func(pos, node, digger), --[[
^ default: minetest.node_dig ^ default: minetest.node_dig
@ -4674,3 +4809,25 @@ The Biome API is still in an experimental phase and subject to change.
-- ^ HTTP status code -- ^ HTTP status code
data = "response" data = "response"
} }
### Authentication handler definition
{
get_auth = func(name),
-- ^ Get authentication data for existing player `name` (`nil` if player doesn't exist)
-- ^ returns following structure `{password=<string>, privileges=<table>, last_login=<number or nil>}`
create_auth = func(name, password),
-- ^ Create new auth data for player `name`
-- ^ Note that `password` is not plain-text but an arbitrary representation decided by the engine
set_password = func(name, password),
-- ^ Set password of player `name` to `password`
Auth data should be created if not present
set_privileges = func(name, privileges),
-- ^ Set privileges of player `name`
-- ^ `privileges` is in table form, auth data should be created if not present
reload = func(),
-- ^ Reload authentication data from the storage location
-- ^ Returns boolean indicating success
record_login = func(name),
-- ^ Called when player joins, used for keeping track of last_login
}

View File

@ -1,4 +1,4 @@
Minetest Lua Mainmenu API Reference 0.4.16 Minetest Lua Mainmenu API Reference 0.4.17
======================================== ========================================
Introduction Introduction

View File

@ -161,11 +161,6 @@
# type: key # type: key
# keymap_cmd_local = . # 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. # Key for toggling unlimited view range.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
# type: key # type: key
@ -344,8 +339,8 @@
# serverlist_file = favoriteservers.txt # serverlist_file = favoriteservers.txt
# Maximum size of the out chat queue. 0 to disable queueing and -1 to make the queue size unlimited # Maximum size of the out chat queue. 0 to disable queueing and -1 to make the queue size unlimited
# type: int min: -1 # type: int
max_out_chat_queue_size = 20 # max_out_chat_queue_size = 20
## Graphics ## Graphics
@ -555,7 +550,7 @@ max_out_chat_queue_size = 20
# type: int # type: int
# screenH = 600 # screenH = 600
# Save the window size automatically when modified. # Save window size automatically when modified.
# type: bool # type: bool
# autosave_screensize = true # autosave_screensize = true
@ -866,11 +861,10 @@ max_out_chat_queue_size = 20
# type: string # type: string
# serverlist_url = servers.minetest.net # serverlist_url = servers.minetest.net
# Disable escape sequences, e.g. chat coloring. # Remove color codes from incoming chat messages
# Use this if you want to run a server with pre-0.4.14 clients and you want to disable # Use this to stop players from being able to use color in their messages
# the escape sequences generated by mods.
# type: bool # type: bool
# disable_escape_sequences = false # strip_color_codes = false
## Network ## 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. # Print the engine's profiling data in regular intervals (in seconds). 0 = disable. Useful for developers.
# type: int # type: int
# profiler_print_interval = 0 # profiler_print_interval = 0

View File

@ -268,7 +268,7 @@ if(WIN32)
else() # Probably MinGW = GCC else() # Probably MinGW = GCC
set(PLATFORM_LIBS "") set(PLATFORM_LIBS "")
endif() 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 # Zlib stuff
set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/../../zlib/zlib-1.2.5" set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/../../zlib/zlib-1.2.5"
@ -325,10 +325,12 @@ else()
endif(HAVE_LIBRT) endif(HAVE_LIBRT)
endif(APPLE) endif(APPLE)
if(NOT APPLE)
# This way Xxf86vm is found on OpenBSD too # This way Xxf86vm is found on OpenBSD too
find_library(XXF86VM_LIBRARY Xxf86vm) find_library(XXF86VM_LIBRARY Xxf86vm)
mark_as_advanced(XXF86VM_LIBRARY) mark_as_advanced(XXF86VM_LIBRARY)
set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY}) set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY})
endif(NOT APPLE)
# Prefer local iconv if installed # Prefer local iconv if installed
find_library(ICONV_LIBRARY iconv) find_library(ICONV_LIBRARY iconv)

View File

@ -388,8 +388,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime,
// *100.0 helps in large map coordinates // *100.0 helps in large map coordinates
m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction); 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 // update the camera position in third-person mode to render blocks behind player
if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) // and correctly apply liquid post FX.
if (m_camera_mode != CAMERA_MODE_FIRST)
m_camera_position = my_cp; m_camera_position = my_cp;
// Get FOV // Get FOV
@ -489,7 +490,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime,
void Camera::updateViewingRange() void Camera::updateViewingRange()
{ {
f32 viewing_range = g_settings->getFloat("viewing_range"); f32 viewing_range = g_settings->getFloat("viewing_range");
f32 near_plane = g_settings->getFloat("near_plane");
m_draw_control.wanted_range = viewing_range; m_draw_control.wanted_range = viewing_range;
m_cameranode->setNearValue(rangelim(near_plane, 0.0f, 0.5f) * BS);
if (m_draw_control.range_all) { if (m_draw_control.range_all) {
m_cameranode->setFarValue(100000.0); m_cameranode->setFarValue(100000.0);
return; return;

View File

@ -332,7 +332,7 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
route_y_min = 0; route_y_min = 0;
// Allow half a diameter + 7 over stone surface // 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 // Limit maximum to area
route_y_max = rangelim(route_y_max, 0, ar.Y - 1); route_y_max = rangelim(route_y_max, 0, ar.Y - 1);

View File

@ -134,7 +134,6 @@ public:
bool large_cave_is_flat; bool large_cave_is_flat;
bool flooded; bool flooded;
s16 max_stone_y;
v3s16 node_min; v3s16 node_min;
v3s16 node_max; v3s16 node_max;

View File

@ -31,7 +31,7 @@
#ifndef __IRR_USTRING_H_INCLUDED__ #ifndef __IRR_USTRING_H_INCLUDED__
#define __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 # define USTRING_CPP0X
# if defined(__GXX_EXPERIMENTAL_CXX0X__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))) # if defined(__GXX_EXPERIMENTAL_CXX0X__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5)))
# define USTRING_CPP0X_NEWLITERALS # define USTRING_CPP0X_NEWLITERALS

View File

@ -73,7 +73,6 @@ Client::Client(
m_connection_reinit_timer(0.1), m_connection_reinit_timer(0.1),
m_avg_rtt_timer(0.0), m_avg_rtt_timer(0.0),
m_playerpos_send_timer(0.0), m_playerpos_send_timer(0.0),
m_ignore_damage_timer(0.0),
m_tsrc(tsrc), m_tsrc(tsrc),
m_shsrc(shsrc), m_shsrc(shsrc),
m_itemdef(itemdef), m_itemdef(itemdef),
@ -239,6 +238,8 @@ Client::~Client()
m_shutdown = true; m_shutdown = true;
m_con.Disconnect(); m_con.Disconnect();
deleteAuthData();
m_mesh_update_thread.stop(); m_mesh_update_thread.stop();
m_mesh_update_thread.wait(); m_mesh_update_thread.wait();
while (!m_mesh_update_thread.m_queue_out.empty()) { while (!m_mesh_update_thread.m_queue_out.empty()) {
@ -266,6 +267,7 @@ Client::~Client()
} }
delete m_minimap; delete m_minimap;
delete m_media_downloader;
} }
void Client::connect(Address address, bool is_local_server) void Client::connect(Address address, bool is_local_server)
@ -283,14 +285,9 @@ void Client::step(float dtime)
DSTACK(FUNCTION_NAME); DSTACK(FUNCTION_NAME);
// Limit a bit // Limit a bit
if(dtime > 2.0) if (dtime > 2.0)
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; m_animation_time += dtime;
if(m_animation_time > 60.0) if(m_animation_time > 60.0)
m_animation_time -= 60.0; m_animation_time -= 60.0;
@ -437,7 +434,6 @@ void Client::step(float dtime)
ClientEnvEvent envEvent = m_env.getClientEnvEvent(); ClientEnvEvent envEvent = m_env.getClientEnvEvent();
if (envEvent.type == CEE_PLAYER_DAMAGE) { 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) if (envEvent.player_damage.send_to_server)
@ -449,7 +445,6 @@ void Client::step(float dtime)
event.player_damage.amount = damage; event.player_damage.amount = damage;
m_client_event_queue.push(event); m_client_event_queue.push(event);
} }
}
// Protocol v29 or greater obsoleted this event // Protocol v29 or greater obsoleted this event
else if (envEvent.type == CEE_PLAYER_BREATH && m_proto_ver < 29) { else if (envEvent.type == CEE_PLAYER_BREATH && m_proto_ver < 29) {
u16 breath = envEvent.player_breath.amount; u16 breath = envEvent.player_breath.amount;

View File

@ -574,7 +574,6 @@ private:
float m_connection_reinit_timer; float m_connection_reinit_timer;
float m_avg_rtt_timer; float m_avg_rtt_timer;
float m_playerpos_send_timer; float m_playerpos_send_timer;
float m_ignore_damage_timer; // Used after server moves player
IntervalLimiter m_map_timer_and_unload_interval; IntervalLimiter m_map_timer_and_unload_interval;
IWritableTextureSource *m_tsrc; IWritableTextureSource *m_tsrc;

View File

@ -60,6 +60,8 @@ struct JoystickButtonCmb : public JoystickCombination {
this->key = key; this->key = key;
} }
virtual ~JoystickButtonCmb() {}
virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const;
u32 filter_mask; u32 filter_mask;
@ -77,6 +79,8 @@ struct JoystickAxisCmb : public JoystickCombination {
this->key = key; this->key = key;
} }
virtual ~JoystickAxisCmb() {}
virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const;
u16 axis_to_compare; u16 axis_to_compare;

View File

@ -1139,13 +1139,14 @@ video::IImage * Align2Npot2(video::IImage * image,
core::dimension2d<u32> dim = image->getDimension(); core::dimension2d<u32> dim = image->getDimension();
std::string extensions = (char*) glGetString(GL_EXTENSIONS);
// Only GLES2 is trusted to correctly report npot support // Only GLES2 is trusted to correctly report npot support
if (get_GL_major_version() > 1 && // Note: we cache the boolean result. GL context will never change on Android.
extensions.find("GL_OES_texture_npot") != std::string::npos) { static const bool hasNPotSupport = get_GL_major_version() > 1 &&
glGetString(GL_EXTENSIONS) &&
strstr(glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
if (hasNPotSupport)
return image; return image;
}
unsigned int height = npot2(dim.Height); unsigned int height = npot2(dim.Height);
unsigned int width = npot2(dim.Width); 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 * 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. * 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) { if (scaleto > 1) {
const core::dimension2d<u32> dim = baseimg->getDimension(); const core::dimension2d<u32> dim = baseimg->getDimension();

View File

@ -159,7 +159,8 @@ enum MaterialType{
TILE_MATERIAL_LIQUID_TRANSPARENT, TILE_MATERIAL_LIQUID_TRANSPARENT,
TILE_MATERIAL_LIQUID_OPAQUE, TILE_MATERIAL_LIQUID_OPAQUE,
TILE_MATERIAL_WAVING_LEAVES, TILE_MATERIAL_WAVING_LEAVES,
TILE_MATERIAL_WAVING_PLANTS TILE_MATERIAL_WAVING_PLANTS,
TILE_MATERIAL_OPAQUE
}; };
// Material flags // Material flags
@ -243,18 +244,20 @@ struct TileLayer
void applyMaterialOptions(video::SMaterial &material) const void applyMaterialOptions(video::SMaterial &material) const
{ {
switch (material_type) { 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_BASIC:
case TILE_MATERIAL_WAVING_LEAVES: case TILE_MATERIAL_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS: case TILE_MATERIAL_WAVING_PLANTS:
material.MaterialTypeParam = 0.5;
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
break; break;
case TILE_MATERIAL_ALPHA: case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT: case TILE_MATERIAL_LIQUID_TRANSPARENT:
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
break; break;
case TILE_MATERIAL_LIQUID_OPAQUE:
material.MaterialType = video::EMT_SOLID;
break;
} }
material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING)
? true : false; ? true : false;

View File

@ -633,6 +633,16 @@ std::vector<u16> ClientInterface::getClientIDs(ClientState min_state)
return reply; 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) void ClientInterface::step(float dtime)
{ {
m_print_info_timer += dtime; m_print_info_timer += dtime;

View File

@ -449,6 +449,9 @@ public:
/* get list of active client id's */ /* get list of active client id's */
std::vector<u16> getClientIDs(ClientState min_state=CS_Active); std::vector<u16> getClientIDs(ClientState min_state=CS_Active);
/* verify is server user limit was reached */
bool isUserLimitReached();
/* get list of client player names */ /* get list of client player names */
const std::vector<std::string> &getPlayerNames() const { return m_clients_names; } const std::vector<std::string> &getPlayerNames() const { return m_clients_names; }
@ -493,7 +496,6 @@ public:
} }
static std::string state2Name(ClientState state); static std::string state2Name(ClientState state);
protected: protected:
//TODO find way to avoid this functions //TODO find way to avoid this functions
void lock() { m_clients_mutex.lock(); } void lock() { m_clients_mutex.lock(); }

View File

@ -294,49 +294,46 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver)
struct MeshBufList 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; video::SMaterial m;
std::vector<scene::IMeshBuffer*> bufs; std::vector<scene::IMeshBuffer*> bufs;
}; };
struct MeshBufListList struct MeshBufListList
{ {
std::vector<MeshBufList> 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<MeshBufList> lists[MAX_TILE_LAYERS];
void clear() void clear()
{ {
lists.clear(); for (int l = 0; l < MAX_TILE_LAYERS; l++)
lists[l].clear();
} }
void add(scene::IMeshBuffer *buf, u8 layer) void add(scene::IMeshBuffer *buf, u8 layer)
{ {
// Append to the correct layer
std::vector<MeshBufList> &list = lists[layer];
const video::SMaterial &m = buf->getMaterial(); const video::SMaterial &m = buf->getMaterial();
for(std::vector<MeshBufList>::iterator i = lists.begin(); for (std::vector<MeshBufList>::iterator it = list.begin(); it != list.end();
i != lists.end(); ++i){ ++it) {
MeshBufList &l = *i;
// comparing a full material is quite expensive so we don't do it if // comparing a full material is quite expensive so we don't do it if
// not even first texture is equal // 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; continue;
if(l.layer != layer) if ((*it).m == m) {
continue; (*it).bufs.push_back(buf);
if (l.m == m) {
l.bufs.push_back(buf);
return; return;
} }
} }
MeshBufList l; MeshBufList l;
l.layer = layer;
l.m = m; l.m = m;
l.bufs.push_back(buf); 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 Measuring time is very useful for long delays when the
machine is swapping a lot. machine is swapping a lot.
*/ */
int time1 = time(0); time_t time1 = time(0);
/* /*
Get animation parameters Get animation parameters
@ -480,15 +477,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
} }
} }
std::vector<MeshBufList> &lists = drawbufs.lists; // Render all layers in order
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
std::vector<MeshBufList> &lists = drawbufs.lists[layer];
int timecheck_counter = 0; int timecheck_counter = 0;
for (std::vector<MeshBufList>::iterator i = lists.begin(); for (std::vector<MeshBufList>::iterator it = lists.begin(); it != lists.end();
i != lists.end(); ++i) { ++it) {
timecheck_counter++; timecheck_counter++;
if (timecheck_counter > 50) { if (timecheck_counter > 50) {
timecheck_counter = 0; timecheck_counter = 0;
int time2 = time(0); time_t time2 = time(0);
if (time2 > time1 + 4) { if (time2 > time1 + 4) {
infostream << "ClientMap::renderMap(): " infostream << "ClientMap::renderMap(): "
"Rendering takes ages, returning." "Rendering takes ages, returning."
@ -497,18 +496,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
} }
} }
MeshBufList &list = *i; driver->setMaterial((*it).m);
driver->setMaterial(list.m); for (std::vector<scene::IMeshBuffer*>::iterator it2 = (*it).bufs.begin();
it2 != (*it).bufs.end(); ++it2) {
for (std::vector<scene::IMeshBuffer*>::iterator j = list.bufs.begin(); driver->drawMeshBuffer(*it2);
j != list.bufs.end(); ++j) { vertex_count += (*it2)->getVertexCount();
scene::IMeshBuffer *buf = *j;
driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount();
meshbuffer_count++; meshbuffer_count++;
} }
}
} }
} // ScopeProfiler } // ScopeProfiler

View File

@ -8,6 +8,7 @@
#define VERSION_MAJOR @VERSION_MAJOR@ #define VERSION_MAJOR @VERSION_MAJOR@
#define VERSION_MINOR @VERSION_MINOR@ #define VERSION_MINOR @VERSION_MINOR@
#define VERSION_PATCH @VERSION_PATCH@ #define VERSION_PATCH @VERSION_PATCH@
#define VERSION_TWEAK @VERSION_TWEAK@
#define VERSION_EXTRA "@VERSION_EXTRA@" #define VERSION_EXTRA "@VERSION_EXTRA@"
#define VERSION_STRING "@VERSION_STRING@" #define VERSION_STRING "@VERSION_STRING@"
#define PRODUCT_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@" #define PRODUCT_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@"

View File

@ -258,27 +258,32 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
//TimeTaker tt2("collisionMoveSimple collect boxes"); //TimeTaker tt2("collisionMoveSimple collect boxes");
ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG);
v3s16 oldpos_i = floatToInt(*pos_f, BS); v3f newpos_f = *pos_f + *speed_f * dtime;
v3s16 newpos_i = floatToInt(*pos_f + *speed_f * dtime, BS); v3f minpos_f(
s16 min_x = MYMIN(oldpos_i.X, newpos_i.X) + (box_0.MinEdge.X / BS) - 1; MYMIN(pos_f->X, newpos_f.X),
s16 min_y = MYMIN(oldpos_i.Y, newpos_i.Y) + (box_0.MinEdge.Y / BS) - 1; MYMIN(pos_f->Y, newpos_f.Y) + 0.01 * BS, // bias rounding, player often at +/-n.5
s16 min_z = MYMIN(oldpos_i.Z, newpos_i.Z) + (box_0.MinEdge.Z / BS) - 1; MYMIN(pos_f->Z, newpos_f.Z)
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; v3f maxpos_f(
s16 max_z = MYMAX(oldpos_i.Z, newpos_i.Z) + (box_0.MaxEdge.Z / BS) + 1; 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; bool any_position_valid = false;
for(s16 x = min_x; x <= max_x; x++) for(s16 x = min.X; x <= max.X; x++)
for(s16 y = min_y; y <= max_y; y++) for(s16 y = min.Y; y <= max.Y; y++)
for(s16 z = min_z; z <= max_z; z++) for(s16 z = min.Z; z <= max.Z; z++)
{ {
v3s16 p(x,y,z); v3s16 p(x,y,z);
bool is_position_valid; bool is_position_valid;
MapNode n = map->getNodeNoEx(p, &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 // Object collides into walkable nodes
any_position_valid = true; any_position_valid = true;
@ -328,7 +333,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
false, n_bouncy_value, p, box)); false, n_bouncy_value, p, box));
} }
} else { } else {
// Collide with unloaded nodes // Collide with unloaded nodes (position invalid) and loaded
// CONTENT_IGNORE nodes (position valid)
aabb3f box = getNodeBox(p, BS); aabb3f box = getNodeBox(p, BS);
cinfo.push_back(NearbyCollisionInfo(true, false, 0, p, box)); 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 // Do not move if world has not loaded yet, since custom node boxes
// are not available for collision detection. // 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) { if (!any_position_valid) {
*speed_f = v3f(0, 0, 0); *speed_f = v3f(0, 0, 0);
return result; return result;

View File

@ -1183,16 +1183,17 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
float moved = lastpos.getDistanceFrom(pos_translator.vect_show); float moved = lastpos.getDistanceFrom(pos_translator.vect_show);
m_step_distance_counter += moved; m_step_distance_counter += moved;
if(m_step_distance_counter > 1.5*BS) if (m_step_distance_counter > 1.5f * BS) {
{ m_step_distance_counter = 0.0f;
m_step_distance_counter = 0; if (!m_is_local_player && m_prop.makes_footstep_sound) {
if(!m_is_local_player && m_prop.makes_footstep_sound)
{
INodeDefManager *ndef = m_client->ndef(); INodeDefManager *ndef = m_client->ndef();
v3s16 p = floatToInt(getPosition() + v3f(0, v3s16 p = floatToInt(getPosition() +
(m_prop.collisionbox.MinEdge.Y-0.5)*BS, 0), BS); v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS);
MapNode n = m_env->getMap().getNodeNoEx(p); MapNode n = m_env->getMap().getNodeNoEx(p);
SimpleSoundSpec spec = ndef->get(n).sound_footstep; 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()); 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_LIGHTING, false);
material.setFlag(video::EMF_BILINEAR_FILTER, 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<u32> &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) m_animated_meshnode->getMaterial(i)
.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
m_animated_meshnode->getMaterial(i) m_animated_meshnode->getMaterial(i)

View File

@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// Corresponding offsets are listed in g_27dirs // Corresponding offsets are listed in g_27dirs
#define FRAMED_NEIGHBOR_COUNT 18 #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), v3s16(-1, -1, 1),
v3s16(-1, 1, -1), v3s16(-1, 1, -1),

View File

@ -59,7 +59,7 @@ public:
m_age += dtime; m_age += dtime;
if(m_age > 10) if(m_age > 10)
{ {
m_removed = true; m_pending_removal = true;
return; return;
} }
@ -406,20 +406,6 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
m_env->getScriptIface()->luaentity_Step(m_id, dtime); m_env->getScriptIface()->luaentity_Step(m_id, dtime);
} }
// Remove LuaEntity beyond terrain edges
{
ServerMap *map = dynamic_cast<ServerMap *>(&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) if(send_recommended == false)
return; return;
@ -555,9 +541,9 @@ int LuaEntitySAO::punch(v3f dir,
ServerActiveObject *puncher, ServerActiveObject *puncher,
float time_from_last_punch) float time_from_last_punch)
{ {
if (!m_registered){ if (!m_registered) {
// Delete unknown LuaEntities when punched // Delete unknown LuaEntities when punched
m_removed = true; m_pending_removal = true;
return 0; return 0;
} }
@ -601,7 +587,7 @@ int LuaEntitySAO::punch(v3f dir,
} }
if (getHP() == 0) 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() void PlayerSAO::disconnected()
{ {
m_peer_id = 0; m_peer_id = 0;
m_removed = true; m_pending_removal = true;
} }
void PlayerSAO::unlinkPlayerSessionAndSave() void PlayerSAO::unlinkPlayerSessionAndSave()
@ -1402,26 +1387,38 @@ bool PlayerSAO::checkMovementCheat()
too, and much more lightweight. 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) { if (m_privs.count("fast") != 0)
// Fast speed player_max_walk = m_player->movement_speed_fast; // Fast speed
player_max_speed = m_player->movement_speed_fast * m_physics_override_speed; else
} else { player_max_walk = m_player->movement_speed_walk; // Normal speed
// Normal speed player_max_walk *= m_physics_override_speed;
player_max_speed = m_player->movement_speed_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,
// Tolerance. The lag pool does this a bit. // until this can be verified correctly, tolerate higher jumping speeds
//player_max_speed *= 2.5; 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); v3f diff = (m_base_position - m_last_good_position);
float d_vert = diff.Y; float d_vert = diff.Y;
diff.Y = 0; diff.Y = 0;
float d_horiz = diff.getLength(); 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) // FIXME: Checking downwards movement is not easily possible currently,
required_time = d_vert / player_max_speed; // Moving upwards // 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)) { if (m_move_pool.grab(required_time)) {
m_last_good_position = m_base_position; m_last_good_position = m_base_position;

View File

@ -923,8 +923,19 @@ public:
<< " against " << def->dump() << std::endl;*/ << " against " << def->dump() << std::endl;*/
if (def->check(input, gamedef)) { 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) // Get output, then decrement input (if requested)
output = def->getOutput(input, gamedef); output = out;
if (decrementInput) if (decrementInput)
def->decrementInput(input, output_replacement, gamedef); def->decrementInput(input, output_replacement, gamedef);
/*errorstream << "Check RETURNS TRUE" << std::endl;*/ /*errorstream << "Check RETURNS TRUE" << std::endl;*/

View File

@ -129,8 +129,9 @@ void set_default_settings(Settings *settings)
settings->setDefault("fps_max", "60"); settings->setDefault("fps_max", "60");
settings->setDefault("pause_fps_max", "10"); settings->setDefault("pause_fps_max", "10");
settings->setDefault("viewing_range", "100"); settings->setDefault("viewing_range", "100");
settings->setDefault("screen_w", "800"); settings->setDefault("near_plane", "0.1");
settings->setDefault("screen_h", "600"); settings->setDefault("screenW", "800");
settings->setDefault("screenH", "600");
settings->setDefault("autosave_screensize", "true"); settings->setDefault("autosave_screensize", "true");
settings->setDefault("fullscreen", "false"); settings->setDefault("fullscreen", "false");
settings->setDefault("fullscreen_bpp", "24"); settings->setDefault("fullscreen_bpp", "24");
@ -259,6 +260,7 @@ void set_default_settings(Settings *settings)
// Server // Server
settings->setDefault("disable_escape_sequences", "false"); settings->setDefault("disable_escape_sequences", "false");
settings->setDefault("strip_color_codes", "false");
// Network // Network
settings->setDefault("enable_ipv6", "true"); settings->setDefault("enable_ipv6", "true");
@ -371,8 +373,8 @@ void set_default_settings(Settings *settings)
// Mobile Platform // Mobile Platform
#if defined(__ANDROID__) || defined(__IOS__) #if defined(__ANDROID__) || defined(__IOS__)
settings->setDefault("screen_w", "0"); settings->setDefault("screenW", "0");
settings->setDefault("screen_h", "0"); settings->setDefault("screenH", "0");
settings->setDefault("fps_max", "35"); settings->setDefault("fps_max", "35");
settings->setDefault("enable_shaders", "false"); settings->setDefault("enable_shaders", "false");
settings->setDefault("fullscreen", "true"); settings->setDefault("fullscreen", "true");

View File

@ -431,8 +431,10 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir,
VMANIP_FLAG_DUNGEON_UNTOUCHABLE, VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
MapNode(dp.c_wall), MapNode(dp.c_wall),
0); 0);
makeHole(p); makeFill(p, dp.holesize, VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
makeHole(p - dir); 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% // TODO: fix stairs code so it works 100%
// (quite difficult) // (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); v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) : v3s16(0, 0, 1);
for (u16 st = 0; st < stair_width; st++) { for (u16 st = 0; st < stair_width; st++) {
if (make_stairs == -1) {
u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z); 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)) && if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) &&
vm->m_data[vi].getContent() == dp.c_wall) 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); vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
}
vi = vm->m_area.index(ps.X, ps.Y, ps.Z); } else if (make_stairs == 1) {
if (vm->m_area.contains(ps) && u32 vi = vm->m_area.index(ps.X, ps.Y - 1, ps.Z);
vm->m_data[vi].getContent() == dp.c_wall) 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); vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
}
}
ps += swv; ps += swv;
} }
} }

View File

@ -146,6 +146,9 @@ EmergeManager::~EmergeManager()
} }
delete thread; delete thread;
// Mapgen init might not be finished if there is an error during startup.
if (m_mapgens.size() > i)
delete m_mapgens[i]; delete m_mapgens[i];
} }
@ -570,6 +573,12 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata,
m_server->setAsyncFatalError("Lua: finishGen" + std::string(e.what())); 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)); EMERGE_DBG_OUT("ended up with: " << analyze_block(block));
/* /*

View File

@ -27,6 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h" #include "log.h"
#include "config.h" #include "config.h"
#include "porting.h" #include "porting.h"
#ifdef __ANDROID__
#include "settings.h" // For g_settings
#endif
namespace fs namespace fs
{ {
@ -741,4 +744,3 @@ bool Rename(const std::string &from, const std::string &to)
} }
} // namespace fs } // namespace fs

View File

@ -341,14 +341,33 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode)
font_path.c_str(), size, true, true, font_shadow, font_path.c_str(), size, true, true, font_shadow,
font_shadow_alpha); font_shadow_alpha);
if (font != NULL) { if (font) {
m_font_cache[mode][basesize] = font; m_font_cache[mode][basesize] = font;
return; return;
} }
if (font_config_prefix == "mono_") {
const std::string &mono_font_path = m_settings->getDefault("mono_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,
mono_font_path.c_str(), size, true, true,
font_shadow, font_shadow_alpha);
if (font) {
m_font_cache[mode][basesize] = font;
return;
}
}
} else {
// try fallback font // try fallback font
errorstream << "FontEngine: failed to load: " << font_path << ", trying to fall back " errorstream << "FontEngine: failed to load: " << font_path <<
"to fallback font" << std::endl; ", trying to fall back to fallback font" << std::endl;
font_path = g_settings->get(font_config_prefix + "fallback_font_path"); font_path = g_settings->get(font_config_prefix + "fallback_font_path");
@ -356,20 +375,39 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode)
font_path.c_str(), size, true, true, font_shadow, font_path.c_str(), size, true, true, font_shadow,
font_shadow_alpha); font_shadow_alpha);
if (font != NULL) { if (font) {
m_font_cache[mode][basesize] = font; m_font_cache[mode][basesize] = font;
return; 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 // give up
#if defined(__ANDROID__) || defined(__IOS__) #if defined(__ANDROID__) || defined(__IOS__)
porting::notifyAbortLoading(); porting::notifyAbortLoading();
#endif #endif
errorstream << "FontEngine: failed to load freetype font: " errorstream << "FontEngine: failed to load freetype font: "
<< font_path << std::endl; << font_path << std::endl;
errorstream << "minetest can not continue without a valid font. Please correct " errorstream << "minetest can not continue without a valid font. "
"the 'font_path' setting or install the font file in the proper " "Please correct the 'font_path' setting or install the font "
"location" << std::endl; "file in the proper location" << std::endl;
abort(); abort();
} }
#endif #endif
@ -471,7 +509,7 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
} }
} }
if (font != NULL) { if (font) {
font->grab(); font->grab();
m_font_cache[mode][basesize] = font; m_font_cache[mode][basesize] = font;
} }

View File

@ -185,6 +185,7 @@ struct LocalFormspecHandler : public TextDest
#endif #endif
// Don't disable this part when modding is disabled, it's used in builtin // Don't disable this part when modding is disabled, it's used in builtin
if (m_client && m_client->getScript())
m_client->getScript()->on_formspec_input(m_formname, fields); m_client->getScript()->on_formspec_input(m_formname, fields);
} }
@ -789,8 +790,8 @@ public:
}; };
bool nodePlacementPrediction(Client &client, bool nodePlacementPrediction(Client &client, const ItemDefinition &playeritem_def,
const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos) const ItemStack &playeritem, v3s16 nodepos, v3s16 neighbourpos)
{ {
std::string prediction = playeritem_def.node_placement_prediction; std::string prediction = playeritem_def.node_placement_prediction;
INodeDefManager *nodedef = client.ndef(); INodeDefManager *nodedef = client.ndef();
@ -833,11 +834,13 @@ bool nodePlacementPrediction(Client &client,
return false; return false;
} }
const ContentFeatures &predicted_f = nodedef->get(id);
// Predict param2 for facedir and wallmounted nodes // Predict param2 for facedir and wallmounted nodes
u8 param2 = 0; u8 param2 = 0;
if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED || if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) { predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
v3s16 dir = nodepos - neighbourpos; v3s16 dir = nodepos - neighbourpos;
if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { 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 || if (predicted_f.param_type_2 == CPT2_FACEDIR ||
nodedef->get(id).param_type_2 == CPT2_COLORED_FACEDIR) { predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS); v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS);
if (abs(dir.X) > abs(dir.Z)) { if (abs(dir.X) > abs(dir.Z)) {
@ -863,7 +866,7 @@ bool nodePlacementPrediction(Client &client,
assert(param2 <= 5); assert(param2 <= 5);
//Check attachment if node is in group attached_node //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] = { static v3s16 wallmounted_dirs[8] = {
v3s16(0, 1, 0), v3s16(0, 1, 0),
v3s16(0, -1, 0), v3s16(0, -1, 0),
@ -874,8 +877,8 @@ bool nodePlacementPrediction(Client &client,
}; };
v3s16 pp; v3s16 pp;
if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED || if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
pp = p + wallmounted_dirs[param2]; pp = p + wallmounted_dirs[param2];
else else
pp = p + v3s16(0, -1, 0); pp = p + v3s16(0, -1, 0);
@ -884,6 +887,28 @@ bool nodePlacementPrediction(Client &client,
return false; 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 // Add node to client map
MapNode n(id, 0, param2); MapNode n(id, 0, param2);
@ -1298,7 +1323,8 @@ protected:
const core::line3d<f32> &shootline, bool liquids_pointable, const core::line3d<f32> &shootline, bool liquids_pointable,
bool look_for_object, const v3s16 &camera_offset); bool look_for_object, const v3s16 &camera_offset);
void handlePointingAtNothing(const ItemStack &playerItem); void handlePointingAtNothing(const ItemStack &playerItem);
void handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def, void handlePointingAtNode(const PointedThing &pointed,
const ItemDefinition &playeritem_def, const ItemStack &playeritem,
const ToolCapabilities &playeritem_toolcap, f32 dtime); const ToolCapabilities &playeritem_toolcap, f32 dtime);
void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
const v3f &player_position, bool show_debug); const v3f &player_position, bool show_debug);
@ -2983,8 +3009,8 @@ void Game::toggleFullViewRange()
}; };
#else #else
static const wchar_t *msg[] = { static const wchar_t *msg[] = {
L"Disabled full viewing range", L"Normal view range",
L"Enabled full viewing range" L"Infinite view range"
}; };
#endif #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) { if (playeritem.name.empty() && hand_def.tool_capabilities != NULL) {
playeritem_toolcap = *hand_def.tool_capabilities; 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) { } else if (pointed.type == POINTEDTHING_OBJECT) {
handlePointingAtObject(pointed, playeritem, player_position, show_debug); handlePointingAtObject(pointed, playeritem, player_position, show_debug);
} else if (isLeftPressed()) { } else if (isLeftPressed()) {
@ -3781,7 +3808,8 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem)
} }
void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinition &playeritem_def, void Game::handlePointingAtNode(const PointedThing &pointed,
const ItemDefinition &playeritem_def, const ItemStack &playeritem,
const ToolCapabilities &playeritem_toolcap, f32 dtime) const ToolCapabilities &playeritem_toolcap, f32 dtime)
{ {
v3s16 nodepos = pointed.node_undersurface; v3s16 nodepos = pointed.node_undersurface;
@ -3833,6 +3861,11 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio
if (meta && meta->getString("formspec") != "" && !random_input if (meta && meta->getString("formspec") != "" && !random_input
&& !isKeyDown(KeyType::SNEAK)) { && !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; infostream << "Launching custom inventory view" << std::endl;
InventoryLocation inventoryloc; InventoryLocation inventoryloc;
@ -3855,7 +3888,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio
// If the wielded item has node placement prediction, // If the wielded item has node placement prediction,
// make that happen // make that happen
bool placed = nodePlacementPrediction(*client, bool placed = nodePlacementPrediction(*client,
playeritem_def, playeritem_def, playeritem,
nodepos, neighbourpos); nodepos, neighbourpos);
if (placed) { if (placed) {

View File

@ -52,12 +52,13 @@ extern wchar_t *utf8_to_wide_c(const char *str);
// The returned string is allocated using new // The returned string is allocated using new
inline const wchar_t *wgettext(const char *str) 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) inline std::string strgettext(const std::string &text)
{ {
return gettext(text.c_str()); return text.empty() ? "" : gettext(text.c_str());
} }
#endif #endif

View File

@ -204,8 +204,8 @@ void GUIChatConsole::draw()
// scale current console height to new window size // scale current console height to new window size
if (m_screensize.Y != 0) if (m_screensize.Y != 0)
m_height = m_height * screensize.Y / m_screensize.Y; m_height = m_height * screensize.Y / m_screensize.Y;
m_desired_height = m_desired_height_fraction * m_screensize.Y;
m_screensize = screensize; m_screensize = screensize;
m_desired_height = m_desired_height_fraction * m_screensize.Y;
reformatConsole(); reformatConsole();
} }
@ -231,6 +231,7 @@ void GUIChatConsole::reformatConsole()
s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
if (cols <= 0 || rows <= 0) if (cols <= 0 || rows <= 0)
cols = rows = 0; cols = rows = 0;
recalculateConsolePosition();
m_chat_backend->reformat(cols, rows); m_chat_backend->reformat(cols, rows);
} }

View File

@ -86,24 +86,30 @@ MenuTextureSource::~MenuTextureSource()
/******************************************************************************/ /******************************************************************************/
video::ITexture* MenuTextureSource::getTexture(const std::string &name, u32 *id) video::ITexture* MenuTextureSource::getTexture(const std::string &name, u32 *id)
{ {
if(id) if (id)
*id = 0; *id = 0;
if(name.empty())
if (name.empty())
return NULL; return NULL;
m_to_delete.insert(name); m_to_delete.insert(name);
#if defined(__ANDROID__) || defined(__IOS__) #if defined(__ANDROID__) || defined(__IOS__)
if (m_driver->findTexture(name.c_str()) != NULL) video::ITexture *retval = m_driver->findTexture(name.c_str());
return m_driver->findTexture(name.c_str()); if (retval)
return retval;
video::IImage *image = m_driver->createImageFromFile(name.c_str()); video::IImage *image = m_driver->createImageFromFile(name.c_str());
if (image) { if (!image)
return NULL;
image = Align2Npot2(image, m_driver); image = Align2Npot2(image, m_driver);
video::ITexture* retval = m_driver->addTexture(name.c_str(), image); retval = m_driver->addTexture(name.c_str(), image);
image->drop(); image->drop();
return retval; return retval;
} #else
#endif
return m_driver->getTexture(name.c_str()); return m_driver->getTexture(name.c_str());
#endif
} }
/******************************************************************************/ /******************************************************************************/

View File

@ -3683,19 +3683,25 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
a->from_i = m_selected_item->i; a->from_i = m_selected_item->i;
m_invmgr->inventoryAction(a); m_invmgr->inventoryAction(a);
} else if (craft_amount > 0) { } else if (craft_amount > 0) {
assert(s.isValid());
// 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 m_selected_content_guess = ItemStack(); // Clear
// Send IACTION_CRAFT
assert(s.isValid());
assert(inv_s); assert(inv_s);
// Send IACTION_CRAFT
infostream << "Handing IACTION_CRAFT to manager" << std::endl; infostream << "Handing IACTION_CRAFT to manager" << std::endl;
ICraftAction *a = new ICraftAction(); ICraftAction *a = new ICraftAction();
a->count = craft_amount; a->count = craft_amount;
a->craft_inv = s.inventoryloc; a->craft_inv = s.inventoryloc;
m_invmgr->inventoryAction(a); m_invmgr->inventoryAction(a);
} }
}
// If m_selected_amount has been decreased to zero, deselect // If m_selected_amount has been decreased to zero, deselect
if (m_selected_amount == 0) { if (m_selected_amount == 0) {

View File

@ -248,6 +248,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);
curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
std::string bind_address = g_settings->get("bind_address"); std::string bind_address = g_settings->get("bind_address");
if (!bind_address.empty()) { if (!bind_address.empty()) {

View File

@ -409,24 +409,32 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
p.Y -= g_settings->getU16("hud_move_upwards"); p.Y -= g_settings->getU16("hud_move_upwards");
v2s32 steppos; v2s32 steppos;
core::rect<s32> srchalfrect, dsthalfrect;
switch (drawdir) { switch (drawdir) {
case HUD_DIR_RIGHT_LEFT: case HUD_DIR_RIGHT_LEFT:
steppos = v2s32(-1, 0); steppos = v2s32(-1, 0);
srchalfrect = core::rect<s32>(srcd.Width / 2, 0, srcd.Width, srcd.Height);
dsthalfrect = core::rect<s32>(dstd.Width / 2, 0, dstd.Width, dstd.Height);
break; break;
case HUD_DIR_TOP_BOTTOM: case HUD_DIR_TOP_BOTTOM:
steppos = v2s32(0, 1); steppos = v2s32(0, 1);
srchalfrect = core::rect<s32>(0, 0, srcd.Width, srcd.Height / 2);
dsthalfrect = core::rect<s32>(0, 0, dstd.Width, dstd.Height / 2);
break; break;
case HUD_DIR_BOTTOM_TOP: case HUD_DIR_BOTTOM_TOP:
steppos = v2s32(0, -1); steppos = v2s32(0, -1);
srchalfrect = core::rect<s32>(0, srcd.Height / 2, srcd.Width, srcd.Height);
dsthalfrect = core::rect<s32>(0, dstd.Height / 2, dstd.Width, dstd.Height);
break; break;
default: default:
steppos = v2s32(1, 0); steppos = v2s32(1, 0);
srchalfrect = core::rect<s32>(0, 0, srcd.Width / 2, srcd.Height);
dsthalfrect = core::rect<s32>(0, 0, dstd.Width / 2, dstd.Height);
} }
steppos.X *= dstd.Width; steppos.X *= dstd.Width;
steppos.Y *= dstd.Height; steppos.Y *= dstd.Height;
for (s32 i = 0; i < count / 2; i++) for (s32 i = 0; i < count / 2; i++) {
{
core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height); core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height); core::rect<s32> 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; p += steppos;
} }
if (count % 2 == 1) if (count % 2 == 1) {
{ dsthalfrect += p;
core::rect<s32> srcrect(0, 0, srcd.Width / 2, srcd.Height); draw2DImageFilterScaled(driver, stat_texture, dsthalfrect, srchalfrect, NULL, colors, true);
core::rect<s32> dstrect(0,0, dstd.Width / 2, dstd.Height);
dstrect += p;
draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
} }
} }

View File

@ -1418,14 +1418,14 @@ void intlGUIEditBox::calculateScrollPos()
// todo: adjust scrollbar // todo: adjust scrollbar
} }
// vertical scroll position if (!WordWrap && !MultiLine)
if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos) return;
VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos;
else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos) // vertical scroll position
VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos; if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y)
else VScrollPos += CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y; // scrolling downwards
VScrollPos = 0; else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y)
VScrollPos += CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y; // scrolling upwards
// todo: adjust scrollbar // todo: adjust scrollbar
} }

View File

@ -659,7 +659,7 @@ bool InventoryList::roomForItem(const ItemStack &item_) const
return false; return false;
} }
bool InventoryList::containsItem(const ItemStack &item) const bool InventoryList::containsItem(const ItemStack &item, bool match_meta) const
{ {
u32 count = item.count; u32 count = item.count;
if(count == 0) if(count == 0)
@ -670,9 +670,9 @@ bool InventoryList::containsItem(const ItemStack &item) const
{ {
if(count == 0) if(count == 0)
break; break;
if(i->name == item.name) if (i->name == item.name
{ && (!match_meta || (i->metadata == item.metadata))) {
if(i->count >= count) if (i->count >= count)
return true; return true;
else else
count -= i->count; count -= i->count;

View File

@ -223,9 +223,10 @@ public:
// Checks whether there is room for a given item // Checks whether there is room for a given item
bool roomForItem(const ItemStack &item) const; 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. // 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 // Removes the given count of the given item name from
// this inventory list. Walks the list in reverse order. // this inventory list. Walks the list in reverse order.

View File

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <vector3d.h> #include <vector3d.h>
typedef core::vector3df v3f; typedef core::vector3df v3f;
typedef core::vector3d<double> v3d;
typedef core::vector3d<s16> v3s16; typedef core::vector3d<s16> v3s16;
typedef core::vector3d<u16> v3u16; typedef core::vector3d<u16> v3u16;
typedef core::vector3d<s32> v3s32; typedef core::vector3d<s32> v3s32;

View File

@ -13,11 +13,11 @@ void ItemStackMetadata::serialize(std::ostream &os) const
{ {
std::ostringstream os2; std::ostringstream os2;
os2 << DESERIALIZE_START; os2 << DESERIALIZE_START;
for (StringMap::const_iterator for (StringMap::const_iterator it = m_stringvars.begin(); it != m_stringvars.end();
it = m_stringvars.begin(); ++it) {
it != m_stringvars.end(); ++it) { if (!(*it).first.empty() || !(*it).second.empty())
os2 << it->first << DESERIALIZE_KV_DELIM os2 << (*it).first << DESERIALIZE_KV_DELIM
<< it->second << DESERIALIZE_PAIR_DELIM; << (*it).second << DESERIALIZE_PAIR_DELIM;
} }
os << serializeJsonStringIfNeeded(os2.str()); os << serializeJsonStringIfNeeded(os2.str());
} }
@ -28,7 +28,8 @@ void ItemStackMetadata::deSerialize(std::istream &is)
m_stringvars.clear(); m_stringvars.clear();
if (!in.empty() && in[0] == DESERIALIZE_START) { if (!in.empty()) {
if (in[0] == DESERIALIZE_START) {
Strfnd fnd(in); Strfnd fnd(in);
fnd.to(1); fnd.to(1);
while (!fnd.at_end()) { while (!fnd.at_end()) {
@ -40,4 +41,5 @@ void ItemStackMetadata::deSerialize(std::istream &is)
// BACKWARDS COMPATIBILITY // BACKWARDS COMPATIBILITY
m_stringvars[""] = in; m_stringvars[""] = in;
} }
}
} }

View File

@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
LocalPlayer::LocalPlayer(Client *client, const char *name): LocalPlayer::LocalPlayer(Client *client, const char *name):
Player(name, client->idef()), Player(name, client->idef()),
parent(0), parent(NULL),
hp(PLAYER_MAX_HP), hp(PLAYER_MAX_HP),
isAttached(false), isAttached(false),
touching_ground(false), touching_ground(false),
@ -53,8 +53,8 @@ LocalPlayer::LocalPlayer(Client *client, const char *name):
overridePosition(v3f(0,0,0)), overridePosition(v3f(0,0,0)),
last_position(v3f(0,0,0)), last_position(v3f(0,0,0)),
last_speed(v3f(0,0,0)), last_speed(v3f(0,0,0)),
last_pitch(0), last_pitch(0.0f),
last_yaw(0), last_yaw(0.0f),
last_keyPressed(0), last_keyPressed(0),
last_camera_fov(0), last_camera_fov(0),
last_wanted_range(0), last_wanted_range(0),
@ -67,13 +67,12 @@ LocalPlayer::LocalPlayer(Client *client, const char *name):
hurt_tilt_timer(0.0f), hurt_tilt_timer(0.0f),
hurt_tilt_strength(0.0f), hurt_tilt_strength(0.0f),
m_position(0,0,0), m_position(0,0,0),
m_sneak_node(32767,32767,32767), 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.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f),
m_sneak_node_bb_top(0,0,0,0,0,0),
m_sneak_node_exists(false), m_sneak_node_exists(false),
m_need_to_get_new_sneak_node(true),
m_sneak_ladder_detected(false), 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(32767,32767,32767),
m_old_node_below_type("air"), m_old_node_below_type("air"),
m_can_jump(false), m_can_jump(false),
@ -96,91 +95,142 @@ LocalPlayer::~LocalPlayer()
{ {
} }
static aabb3f getTopBoundingBox(const std::vector<aabb3f> &nodeboxes) static aabb3f getNodeBoundingBox(const std::vector<aabb3f> &nodeboxes)
{ {
if (nodeboxes.size() == 0)
return aabb3f(0, 0, 0, 0, 0, 0);
aabb3f b_max; aabb3f b_max;
b_max.reset(-BS, -BS, -BS);
for (std::vector<aabb3f>::const_iterator it = nodeboxes.begin(); std::vector<aabb3f>::const_iterator it = nodeboxes.begin();
it != nodeboxes.end(); ++it) { b_max = aabb3f(it->MinEdge, it->MaxEdge);
aabb3f box = *it;
if (box.MaxEdge.Y > b_max.MaxEdge.Y) ++it;
b_max = box; for (; it != nodeboxes.end(); ++it)
else if (box.MaxEdge.Y == b_max.MaxEdge.Y) b_max.addInternalBox(*it);
b_max.addInternalBox(box);
} return b_max;
return aabb3f(v3f(b_max.MinEdge.X, b_max.MaxEdge.Y, b_max.MinEdge.Z), b_max.MaxEdge);
} }
#define GETNODE(map, p3, v2, y, valid) \ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
(map)->getNodeNoEx((p3) + v3s16((v2).X, y, (v2).Y), valid) const v3f &sneak_max)
// pos is the node the player is standing inside(!)
static bool detectSneakLadder(Map *map, INodeDefManager *nodemgr, v3s16 pos)
{ {
// Detects a structure known as "sneak ladder" or "sneak elevator" static const v3s16 dir9_center[9] = {
// that relies on bugs to provide a fast means of vertical transportation, v3s16( 0, 0, 0),
// the bugs have since been fixed but this function remains to keep it working. v3s16( 1, 0, 0),
// NOTE: This is just entirely a huge hack and causes way too many problems. v3s16(-1, 0, 0),
bool is_valid_position; 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; MapNode node;
// X/Z vectors for 4 neighboring nodes bool is_valid_position;
static const v2s16 vecs[] = { v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1) }; bool new_sneak_node_exists = m_sneak_node_exists;
for (u16 i = 0; i < ARRLEN(vecs); i++) { // We want the top of the sneak node to be below the players feet
const v2s16 vec = vecs[i]; 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 // Get position of current standing node
node = GETNODE(map, pos, vec, 0, &is_valid_position); const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
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)
continue;
// check one more node above OR below with corresponding walkability if (current_node != m_sneak_node) {
node = GETNODE(map, pos, vec, -1, &is_valid_position); new_sneak_node_exists = false;
bool ok = is_valid_position && w != nodemgr->get(node).walkable; } else {
if (!ok) { node = map->getNodeNoEx(current_node, &is_valid_position);
node = GETNODE(map, pos, vec, 2, &is_valid_position); if (!is_valid_position || !nodemgr->get(node).walkable)
ok = is_valid_position && w == nodemgr->get(node).walkable; new_sneak_node_exists = false;
} }
if (ok) // Keep old sneak node
if (new_sneak_node_exists)
return true; 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;
// 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;
m_sneak_node = p;
new_sneak_node_exists = true;
} }
if (!new_sneak_node_exists)
return false; return false;
}
static bool detectLedge(Map *map, INodeDefManager *nodemgr, v3s16 pos) // Update saved top bounding box of sneak node
{ node = map->getNodeNoEx(m_sneak_node);
bool is_valid_position; std::vector<aabb3f> nodeboxes;
MapNode node; node.getCollisionBoxes(nodemgr, &nodeboxes);
// X/Z vectors for 4 neighboring nodes m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes);
static const v2s16 vecs[] = {v2s16(-1, 0), v2s16(1, 0), v2s16(0, -1), v2s16(0, 1)};
for (u16 i = 0; i < ARRLEN(vecs); i++) { if (physics_override_sneak_glitch) {
const v2s16 vec = vecs[i]; // Detect sneak ladder:
// Node two meters above sneak node must be solid
node = GETNODE(map, pos, vec, 1, &is_valid_position); node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0),
&is_valid_position);
if (is_valid_position && nodemgr->get(node).walkable) { if (is_valid_position && nodemgr->get(node).walkable) {
// Ledge exists // Node three meters above: must be non-solid
node = GETNODE(map, pos, vec, 2, &is_valid_position); node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0),
if (is_valid_position && !nodemgr->get(node).walkable) &is_valid_position);
// Space above ledge exists m_sneak_ladder_detected = is_valid_position &&
!nodemgr->get(node).walkable;
}
}
return true; return true;
}
}
return false;
} }
#undef GETNODE
void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
std::vector<CollisionInfo> *collision_info) std::vector<CollisionInfo> *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 // Temporary option for old move code
if (!physics_override_new_move) { if (!physics_override_new_move) {
old_move(dtime, env, pos_max_d, collision_info); 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(); v3f position = getPosition();
// Copy parent position if local player is attached // Copy parent position if local player is attached
if(isAttached) if (isAttached) {
{
setPosition(overridePosition); setPosition(overridePosition);
m_sneak_node_exists = false;
return; return;
} }
@ -204,11 +252,11 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
bool fly_allowed = m_client->checkLocalPrivilege("fly"); bool fly_allowed = m_client->checkLocalPrivilege("fly");
bool noclip = m_client->checkLocalPrivilege("noclip") && bool noclip = m_client->checkLocalPrivilege("noclip") &&
g_settings->getBool("noclip"); g_settings->getBool("noclip");
bool free_move = noclip && fly_allowed && g_settings->getBool("free_move"); bool free_move = g_settings->getBool("free_move") && fly_allowed;
if (free_move) {
if (noclip && free_move) {
position += m_speed * dtime; position += m_speed * dtime;
setPosition(position); setPosition(position);
m_sneak_node_exists = false;
return; return;
} }
@ -279,7 +327,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
|| nodemgr->get(node2.getContent()).climbable) && !free_move; || nodemgr->get(node2.getContent()).climbable) && !free_move;
} }
/* /*
Collision uncertainty radius Collision uncertainty radius
Make it a bit larger than the maximum distance of movement Make it a bit larger than the maximum distance of movement
@ -291,28 +338,85 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
// This should always apply, otherwise there are glitches // This should always apply, otherwise there are glitches
sanity_check(d > pos_max_d); 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<CollisionInfo>::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 // Max. distance (X, Z) over border for sneaking determined by collision box
// * 0.49 to keep the center just barely on the node // * 0.49 to keep the center just barely on the node
v3f sneak_max = m_collisionbox.getExtent() * 0.49; v3f sneak_max = m_collisionbox.getExtent() * 0.49;
if (m_sneak_ladder_detected) { if (m_sneak_ladder_detected) {
// restore legacy behaviour (this makes the m_speed.Y hack necessary) // restore legacy behaviour (this makes the m_speed.Y hack necessary)
sneak_max = v3f(0.4 * BS, 0, 0.4 * BS); sneak_max = v3f(0.4 * BS, 0, 0.4 * BS);
} }
/* /*
If sneaking, keep in range from the last walked node and don't If sneaking, keep on top of last walked node and don't fall off
fall off from it
*/ */
if (control.sneak && m_sneak_node_exists && if (could_sneak && m_sneak_node_exists) {
!(fly_allowed && g_settings->getBool("free_move")) &&
!in_liquid && !is_climbing &&
physics_override_sneak) {
const v3f sn_f = intToFloat(m_sneak_node, BS); const v3f sn_f = intToFloat(m_sneak_node, BS);
const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge; const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge;
const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge; const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge;
const v3f old_pos = position; const v3f old_pos = position;
const v3f old_speed = m_speed; const v3f old_speed = m_speed;
f32 y_diff = bmax.Y - position.Y;
m_standing_node = m_sneak_node;
// (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, position.X = rangelim(position.X,
bmin.X - sneak_max.X, bmax.X + sneak_max.X); bmin.X - sneak_max.X, bmax.X + sneak_max.X);
position.Z = rangelim(position.Z, position.Z = rangelim(position.Z,
@ -322,18 +426,21 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
m_speed.X = 0; m_speed.X = 0;
if (position.Z != old_pos.Z) if (position.Z != old_pos.Z)
m_speed.Z = 0; 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 (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) { m_speed.Y - old_speed.Y > BS) {
// Collide with sneak node, report fall damage // Collide with sneak node, report fall damage
CollisionInfo sn_info; 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 Find the next sneak node if necessary
set to true.
Player is allowed to jump when this is true.
*/ */
bool touching_ground_was = touching_ground; bool new_sneak_node_exists = false;
touching_ground = result.touching_ground;
// We want the top of the sneak node to be below the players feet if (could_sneak)
f32 position_y_mod; new_sneak_node_exists = updateSneakNode(map, position, sneak_max);
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<aabb3f> 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));
/* /*
Set new position but keep sneak node set Set new position but keep sneak node set
*/ */
bool sneak_node_exists = m_sneak_node_exists;
setPosition(position); setPosition(position);
m_sneak_node_exists = sneak_node_exists; m_sneak_node_exists = new_sneak_node_exists;
/* /*
Report collisions Report collisions
*/ */
// Dont report if flying
if(collision_info && !(g_settings->getBool("free_move") && fly_allowed)) {
for(size_t i=0; i<result.collisions.size(); i++) {
const CollisionInfo &info = result.collisions[i];
collision_info->push_back(info);
}
}
if(!result.standing_on_object && !touching_ground_was && touching_ground) { if(!result.standing_on_object && !touching_ground_was && touching_ground) {
MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround"); MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround");
m_client->event()->put(e); 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 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 // 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")) if (itemgroup_get(f.groups, "disable_jump"))
m_can_jump = false; 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 // Jump key pressed while jumping off from a bouncy block
if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
@ -705,17 +685,8 @@ void LocalPlayer::applyControl(float dtime)
*/ */
v3f speedJ = getSpeed(); v3f speedJ = getSpeed();
if(speedJ.Y >= -0.5 * BS) { 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; speedJ.Y = movement_speed_jump * physics_override_jump;
setSpeed(speedJ); setSpeed(speedJ);
}
MtEvent *e = new SimpleTriggerEvent("PlayerJump"); MtEvent *e = new SimpleTriggerEvent("PlayerJump");
m_client->event()->put(e); m_client->event()->put(e);
@ -772,7 +743,7 @@ v3s16 LocalPlayer::getStandingNodePos()
{ {
if(m_sneak_node_exists) if(m_sneak_node_exists)
return m_sneak_node; return m_sneak_node;
return floatToInt(getPosition() - v3f(0, BS, 0), BS); return m_standing_node;
} }
v3s16 LocalPlayer::getFootstepNodePos() 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 // this shouldn't be hardcoded but transmitted from server
float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2); float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2);
#ifdef __ANDROID__ #if defined(__ANDROID__) || defined(__IOS__)
player_stepheight += (0.6 * BS); player_stepheight += (0.6 * BS);
#endif #endif

View File

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "player.h" #include "player.h"
#include "environment.h" #include "environment.h"
#include "constants.h"
#include <list> #include <list>
class Client; class Client;
@ -46,6 +47,8 @@ public:
ClientActiveObject *parent; ClientActiveObject *parent;
// Initialize hp to 0, so that no hearts will be shown if server
// doesn't support health points
u16 hp; u16 hp;
bool isAttached; bool isAttached;
bool touching_ground; bool touching_ground;
@ -108,7 +111,7 @@ public:
void setCAO(GenericCAO *toset) void setCAO(GenericCAO *toset)
{ {
assert(m_cao == NULL); // Pre-condition assert(!m_cao); // Pre-condition
m_cao = toset; m_cao = toset;
} }
@ -140,30 +143,31 @@ public:
private: private:
void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); void accelerateHorizontal(const v3f &target_speed, const f32 max_increase);
void accelerateVertical(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; v3f m_position;
v3s16 m_standing_node;
v3s16 m_sneak_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 // Stores the top bounding box of m_sneak_node
aabb3f m_sneak_node_bb_top; aabb3f m_sneak_node_bb_top;
// Whether the player is allowed to sneak // Whether the player is allowed to sneak
bool m_sneak_node_exists; 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 // Whether a "sneak ladder" structure is detected at the players pos
// see detectSneakLadder() in the .cpp for more info (always false if disabled) // see detectSneakLadder() in the .cpp for more info (always false if disabled)
bool m_sneak_ladder_detected; 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, // Node below player, used to determine whether it has been removed,
// and its old type // and its old type
v3s16 m_old_node_below; v3s16 m_old_node_below;
std::string m_old_node_below_type; std::string m_old_node_below_type;
// ***** End of variables for temporary option *****
bool m_can_jump; bool m_can_jump;
u16 m_breath; u16 m_breath;
f32 m_yaw; f32 m_yaw;

View File

@ -373,13 +373,10 @@ void StringBuffer::push_back(char c)
flush(std::string(buffer, buffer_index)); flush(std::string(buffer, buffer_index));
buffer_index = 0; buffer_index = 0;
} else { } else {
int index = buffer_index; buffer[buffer_index++] = c;
buffer[index++] = c; if (buffer_index >= BUFFER_LENGTH) {
if (index >= BUFFER_LENGTH) {
flush(std::string(buffer, buffer_index)); flush(std::string(buffer, buffer_index));
buffer_index = 0; buffer_index = 0;
} else {
buffer_index = index;
} }
} }
} }

View File

@ -1377,11 +1377,6 @@ s16 ServerMap::getWaterLevel()
return getMapgenParams()->water_level; return getMapgenParams()->water_level;
} }
bool ServerMap::saoPositionOverLimit(const v3f &p)
{
return getMapgenParams()->saoPosOverLimit(p);
}
bool ServerMap::blockpos_over_mapgen_limit(v3s16 p) bool ServerMap::blockpos_over_mapgen_limit(v3s16 p)
{ {
const s16 mapgen_limit_bp = rangelim( const s16 mapgen_limit_bp = rangelim(
@ -2734,6 +2729,7 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
continue; continue;
block->copyFrom(*this); block->copyFrom(*this);
block->raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_VMANIP);
if(modified_blocks) if(modified_blocks)
(*modified_blocks)[p] = block; (*modified_blocks)[p] = block;

View File

@ -377,8 +377,6 @@ public:
*/ */
ServerMapSector *createSector(v2s16 p); ServerMapSector *createSector(v2s16 p);
bool saoPositionOverLimit(const v3f &p);
/* /*
Blocks are generated by using these and makeBlock(). Blocks are generated by using these and makeBlock().
*/ */

View File

@ -122,7 +122,8 @@ public:
#define MOD_REASON_STATIC_DATA_REMOVED (1 << 16) #define MOD_REASON_STATIC_DATA_REMOVED (1 << 16)
#define MOD_REASON_STATIC_DATA_CHANGED (1 << 17) #define MOD_REASON_STATIC_DATA_CHANGED (1 << 17)
#define MOD_REASON_EXPIRE_DAYNIGHTDIFF (1 << 18) #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 //// MapBlock itself

View File

@ -815,7 +815,16 @@ void MapgenBasic::dustTopNodes()
} }
content_t c = vm->m_data[vi].getContent(); 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_area.add_y(em, vi, 1);
vm->m_data[vi] = MapNode(biome->c_dust); vm->m_data[vi] = MapNode(biome->c_dust);
} }
@ -979,8 +988,7 @@ bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id)
void GenerateNotifier::getEvents( void GenerateNotifier::getEvents(
std::map<std::string, std::vector<v3s16> > &event_map, std::map<std::string, std::vector<v3s16> > &event_map)
bool peek_events)
{ {
std::list<GenNotifyEvent>::iterator it; std::list<GenNotifyEvent>::iterator it;
@ -992,8 +1000,11 @@ void GenerateNotifier::getEvents(
event_map[name].push_back(gn.pos); event_map[name].push_back(gn.pos);
} }
}
if (!peek_events)
void GenerateNotifier::clearEvents()
{
m_notify_events.clear(); m_notify_events.clear();
} }
@ -1055,16 +1066,15 @@ void MapgenParams::writeParams(Settings *settings) const
bparams->writeParams(settings); 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() void MapgenParams::calcMapgenEdges()
{ {
// Central chunk offset, in blocks // Central chunk offset, in blocks
s16 ccoff_b = -chunksize / 2; s16 ccoff_b = -chunksize / 2;
// Chunksize, in nodes // Chunksize, in nodes
s32 csize_n = chunksize * MAP_BLOCKSIZE; s32 csize_n = chunksize * MAP_BLOCKSIZE;
// Minp/maxp of central chunk, in nodes // Minp/maxp of central chunk, in nodes
s16 ccmin = ccoff_b * MAP_BLOCKSIZE; s16 ccmin = ccoff_b * MAP_BLOCKSIZE;
s16 ccmax = ccmin + csize_n - 1; s16 ccmax = ccmin + csize_n - 1;
@ -1083,25 +1093,17 @@ void MapgenParams::calcMapgenEdges()
s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0); s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0);
s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0); s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0);
// Mapgen edges, in nodes // Mapgen edges, in nodes
// These values may be useful later as additional class members mapgen_edge_min = ccmin - numcmin * csize_n;
s16 mapgen_edge_min = ccmin - numcmin * csize_n; mapgen_edge_max = ccmax + numcmax * csize_n;
s16 mapgen_edge_max = ccmax + numcmax * csize_n;
// SAO position limits, in Irrlicht units m_mapgen_edges_calculated = true;
m_sao_limit_min = mapgen_edge_min * BS - 3.0f;
m_sao_limit_max = mapgen_edge_max * BS + 3.0f;
} }
bool MapgenParams::saoPosOverLimit(const v3f &p) s32 MapgenParams::getSpawnRangeMax()
{ {
if (!m_sao_limit_calculated) { if (!m_mapgen_edges_calculated)
calcMapgenEdges(); calcMapgenEdges();
m_sao_limit_calculated = true;
} return MYMIN(-mapgen_edge_min, mapgen_edge_max);
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;
} }

View File

@ -101,8 +101,8 @@ public:
void setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids); void setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids);
bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0); bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0);
void getEvents(std::map<std::string, std::vector<v3s16> > &event_map, void getEvents(std::map<std::string, std::vector<v3s16> > &event_map);
bool peek_events=false); void clearEvents();
private: private:
u32 m_notify_on; u32 m_notify_on;
@ -132,6 +132,9 @@ struct MapgenParams {
BiomeParams *bparams; BiomeParams *bparams;
s16 mapgen_edge_min;
s16 mapgen_edge_max;
MapgenParams() : MapgenParams() :
mgtype(MAPGEN_DEFAULT), mgtype(MAPGEN_DEFAULT),
chunksize(5), chunksize(5),
@ -140,9 +143,10 @@ struct MapgenParams {
mapgen_limit(MAX_MAP_GENERATION_LIMIT), mapgen_limit(MAX_MAP_GENERATION_LIMIT),
flags(MG_CAVES | MG_LIGHT | MG_DECORATIONS), flags(MG_CAVES | MG_LIGHT | MG_DECORATIONS),
bparams(NULL), bparams(NULL),
m_sao_limit_min(MAX_MAP_GENERATION_LIMIT * BS),
m_sao_limit_max(MAX_MAP_GENERATION_LIMIT * BS), mapgen_edge_min(-MAX_MAP_GENERATION_LIMIT),
m_sao_limit_calculated(false) 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 readParams(const Settings *settings);
virtual void writeParams(Settings *settings) const; virtual void writeParams(Settings *settings) const;
bool saoPosOverLimit(const v3f &p); s32 getSpawnRangeMax();
private: private:
void calcMapgenEdges(); void calcMapgenEdges();
float m_sao_limit_min; bool m_mapgen_edges_calculated;
float m_sao_limit_max;
bool m_sao_limit_calculated;
}; };

View File

@ -57,13 +57,17 @@ MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge)
this->spflags = params->spflags; this->spflags = params->spflags;
this->cave_width = params->cave_width; this->cave_width = params->cave_width;
this->float_mount_density = params->float_mount_density; this->float_mount_density = params->float_mount_density;
this->float_mount_height = params->float_mount_height;
this->floatland_level = params->floatland_level; this->floatland_level = params->floatland_level;
this->shadow_limit = params->shadow_limit; this->shadow_limit = params->shadow_limit;
this->cavern_limit = params->cavern_limit; this->cavern_limit = params->cavern_limit;
this->cavern_taper = params->cavern_taper; this->cavern_taper = params->cavern_taper;
this->cavern_threshold = params->cavern_threshold; 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 // 2D noise
noise_terrain_base = new Noise(&params->np_terrain_base, seed, csize.X, csize.Z); noise_terrain_base = new Noise(&params->np_terrain_base, seed, csize.X, csize.Z);
noise_terrain_alt = new Noise(&params->np_terrain_alt, seed, csize.X, csize.Z); noise_terrain_alt = new Noise(&params->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) 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 density_gradient = -((float)y / mnt_h_n);
float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed); 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) 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 density_gradient = -((float)y / mounthn);
float mountn = noise_mountain->result[idx_xyz]; 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]; float n_base = noise_floatland_base->result[idx_xz];
if (n_base > 0.0f) { 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 amp = n_base * n_base_height;
float ridge = n_base_height / 3.0f; float ridge = n_base_height / 3.0f;
base_min = floatland_level - amp / 1.5f; base_min = floatland_level - amp / 1.5f;

View File

@ -249,18 +249,15 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox,
int facedir = n.getFaceDir(nodemgr); int facedir = n.getFaceDir(nodemgr);
u8 axisdir = facedir>>2; u8 axisdir = facedir>>2;
facedir&=0x03; facedir&=0x03;
for(std::vector<aabb3f>::const_iterator for (std::vector<aabb3f>::const_iterator
i = fixed.begin(); i = fixed.begin();
i != fixed.end(); ++i) i != fixed.end(); ++i) {
{
aabb3f box = *i; aabb3f box = *i;
if (nodebox.type == NODEBOX_LEVELED) { if (nodebox.type == NODEBOX_LEVELED)
box.MaxEdge.Y = -BS/2 + BS*((float)1/LEVELED_MAX) * n.getLevel(nodemgr); box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * BS;
}
switch (axisdir) switch (axisdir) {
{
case 0: case 0:
if(facedir == 1) if(facedir == 1)
{ {

View File

@ -103,8 +103,8 @@ enum Rotation {
#define LIQUID_INFINITY_MASK 0x80 //0b10000000 #define LIQUID_INFINITY_MASK 0x80 //0b10000000
// mask for param2, now as for liquid // mask for leveled nodebox param2
#define LEVELED_MASK 0x3F #define LEVELED_MASK 0x7F
#define LEVELED_MAX LEVELED_MASK #define LEVELED_MAX LEVELED_MASK

View File

@ -580,10 +580,6 @@ void Client::handleCommand_MovePlayer(NetworkPacket* pkt)
event.player_force_move.pitch = pitch; event.player_force_move.pitch = pitch;
event.player_force_move.yaw = yaw; event.player_force_move.yaw = yaw;
m_client_event_queue.push(event); 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) void Client::handleCommand_DeathScreen(NetworkPacket* pkt)
@ -1172,9 +1168,21 @@ void Client::handleCommand_HudSetParam(NetworkPacket* pkt)
player->hud_hotbar_itemcount = hotbar_itemcount; player->hud_hotbar_itemcount = hotbar_itemcount;
} }
else if (param == HUD_PARAM_HOTBAR_IMAGE) { 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; player->hotbar_image = value;
} }
else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { 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; player->hotbar_selected_image = value;
} }
} }

View File

@ -58,9 +58,11 @@ void NetworkPacket::putRawPacket(u8 *data, u32 datasize, u16 peer_id)
m_datasize = datasize - 2; m_datasize = datasize - 2;
m_peer_id = peer_id; m_peer_id = peer_id;
m_data.resize(m_datasize);
// split command and datas // split command and datas
m_command = readU16(&data[0]); m_command = readU16(&data[0]);
m_data = std::vector<u8>(&data[2], &data[2 + m_datasize]); memcpy(m_data.data(), &data[2], m_datasize);
} }
const char* NetworkPacket::getString(u32 from_offset) const char* NetworkPacket::getString(u32 from_offset)

View File

@ -211,7 +211,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
// Enforce user limit. // Enforce user limit.
// Don't enforce for users that have some admin right // 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, "server") &&
!checkPriv(playername, "ban") && !checkPriv(playername, "ban") &&
!checkPriv(playername, "privs") && !checkPriv(playername, "privs") &&
@ -520,7 +520,7 @@ void Server::handleCommand_Init_Legacy(NetworkPacket* pkt)
// Enforce user limit. // Enforce user limit.
// Don't enforce for users that have some admin right // 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, "server") &&
!checkPriv(playername, "ban") && !checkPriv(playername, "ban") &&
!checkPriv(playername, "privs") && !checkPriv(playername, "privs") &&
@ -943,6 +943,18 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
(ma->to_inv.type == InventoryLocation::PLAYER) && (ma->to_inv.type == InventoryLocation::PLAYER) &&
(ma->to_inv.name == player->getName()); (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 Disable moving items out of craftpreview
*/ */
@ -1257,6 +1269,37 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt)
// the previous addition has been successfully removed // 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) 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"); static const bool enable_anticheat = !g_settings->getBool("disable_anticheat");
if ((action == 0 || action == 2 || action == 3 || action == 4) && if ((action == 0 || action == 2 || action == 3 || action == 4) &&
(enable_anticheat && !isSingleplayer())) { enable_anticheat && !isSingleplayer()) {
float d = player_pos.getDistanceFrom(pointed_pos_under); float d = player_pos.getDistanceFrom(pointed_pos_under);
const ItemDefinition &playeritem_def = if (!checkInteractDistance(player, d, pointed.dump())) {
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;
// Re-send block to revert change on client-side // Re-send block to revert change on client-side
RemoteClient *client = getClient(pkt->getPeerId()); RemoteClient *client = getClient(pkt->getPeerId());
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
client->SetBlockNotSent(blockpos); client->SetBlockNotSent(blockpos);
// Call callbacks
m_script->on_cheat(playersao, "interacted_too_far");
// Do nothing else
return; return;
} }
} }
@ -1441,8 +1464,8 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
playersao->noCheatDigStart(p_under); playersao->noCheatDigStart(p_under);
} }
else if (pointed.type == POINTEDTHING_OBJECT) { else if (pointed.type == POINTEDTHING_OBJECT) {
// Skip if object has been removed // Skip if object can't be interacted with anymore
if (pointed_object->m_removed) if (pointed_object->isGone())
return; return;
actionstream<<player->getName()<<" punches object " actionstream<<player->getName()<<" punches object "
@ -1600,8 +1623,8 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
if (pointed.type == POINTEDTHING_OBJECT) { if (pointed.type == POINTEDTHING_OBJECT) {
// Right click object // Right click object
// Skip if object has been removed // Skip if object can't be interacted with anymore
if (pointed_object->m_removed) if (pointed_object->isGone())
return; return;
actionstream << player->getName() << " right-clicks object " actionstream << player->getName() << " right-clicks object "

View File

@ -682,6 +682,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
switch (drawtype) { switch (drawtype) {
default: default:
case NDT_NORMAL: case NDT_NORMAL:
material_type = (alpha == 255) ?
TILE_MATERIAL_OPAQUE : TILE_MATERIAL_ALPHA;
solidness = 2; solidness = 2;
break; break;
case NDT_AIRLIKE: case NDT_AIRLIKE:
@ -778,6 +780,16 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
tile_shader[j] = shdsrc->getShader("nodes_shader", tile_shader[j] = shdsrc->getShader("nodes_shader",
material_type, drawtype); 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[]) // Tiles (fill in f->tiles[])
for (u16 j = 0; j < 6; j++) { for (u16 j = 0; j < 6; j++) {
@ -786,8 +798,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
tdef[j].backface_culling, material_type); tdef[j].backface_culling, material_type);
if (tdef_overlay[j].name != "") if (tdef_overlay[j].name != "")
fillTileAttribs(tsrc, &tiles[j].layers[1], &tdef_overlay[j], fillTileAttribs(tsrc, &tiles[j].layers[1], &tdef_overlay[j],
tile_shader[j], tsettings.use_normal_texture, overlay_shader[j], tsettings.use_normal_texture,
tdef[j].backface_culling, material_type); tdef[j].backface_culling, overlay_material);
} }
// Special tiles (fill in f->special_tiles[]) // Special tiles (fill in f->special_tiles[])

View File

@ -130,7 +130,9 @@ s32 PcgRandom::range(s32 min, s32 max)
if (max < min) if (max < min)
throw PrngException("Invalid range (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; return range(bound) + min;
} }

View File

@ -290,6 +290,59 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr,
ParticleSpawner::~ParticleSpawner() {} 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) void ParticleSpawner::step(float dtime, ClientEnvironment* env)
{ {
m_time += dtime; m_time += dtime;
@ -311,122 +364,33 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env)
} }
} }
if (m_spawntime != 0) // Spawner exists for a predefined timespan if (m_spawntime != 0) {
{ // Spawner exists for a predefined timespan
for(std::vector<float>::iterator i = m_spawntimes.begin(); for (std::vector<float>::iterator i = m_spawntimes.begin();
i != m_spawntimes.end();) i != m_spawntimes.end();) {
{ if ((*i) <= m_time && m_amount > 0) {
if ((*i) <= m_time && m_amount > 0)
{
m_amount--; m_amount--;
// Pretend to, but don't actually spawn a particle if it is // Pretend to, but don't actually spawn a particle if it is
// attached to an unloaded object or distant from player. // attached to an unloaded object or distant from player.
if (!unloaded) { if (!unloaded)
v3f ppos = m_player->getPosition() / BS; spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
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);
}
}
i = m_spawntimes.erase(i); i = m_spawntimes.erase(i);
} } else {
else
{
++i; ++i;
} }
} }
} } else {
else // Spawner exists for an infinity timespan, spawn on a per-second base // Spawner exists for an infinity timespan, spawn on a per-second base
{
// Skip this step if attached to an unloaded object // Skip this step if attached to an unloaded object
if (unloaded) if (unloaded)
return; 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) { for (int i = 0; i <= m_amount; i++) {
v3f vel = random_v3f(m_minvel, m_maxvel); if (rand() / (float)RAND_MAX < dtime)
v3f acc = random_v3f(m_minacc, m_maxacc); spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
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);
}
}
} }
} }
} }

View File

@ -117,7 +117,7 @@ private:
class ParticleSpawner class ParticleSpawner
{ {
public: public:
ParticleSpawner(IGameDef* gamedef, ParticleSpawner(IGameDef* gamedef,
scene::ISceneManager *smgr, scene::ISceneManager *smgr,
LocalPlayer *player, LocalPlayer *player,
@ -144,8 +144,12 @@ class ParticleSpawner
bool get_expired () bool get_expired ()
{ return (m_amount <= 0) && m_spawntime != 0; } { return (m_amount <= 0) && m_spawntime != 0; }
private: private:
ParticleManager* m_particlemanager; void spawnParticle(ClientEnvironment *env, float radius,
bool is_attached, const v3f &attached_pos,
float attached_yaw);
ParticleManager *m_particlemanager;
float m_time; float m_time;
IGameDef *m_gamedef; IGameDef *m_gamedef;
scene::ISceneManager *m_smgr; scene::ISceneManager *m_smgr;

View File

@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <windows.h> #include <windows.h>
#include <wincrypt.h> #include <wincrypt.h>
#include <algorithm> #include <algorithm>
#include <shlwapi.h>
#endif #endif
#if !defined(_WIN32) #if !defined(_WIN32)
#include <unistd.h> #include <unistd.h>
@ -182,20 +183,26 @@ bool detectMSVCBuildDir(const std::string &path)
std::string get_sysinfo() std::string get_sysinfo()
{ {
#ifdef _WIN32 #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 << "." std::ostringstream oss;
<< osvi.dwMinorVersion; LPSTR filePath = new char[MAX_PATH];
if (osvi.szCSDVersion[0]) UINT blockSize;
oss << "-" << tmp; VS_FIXEDFILEINFO *fixedFileInfo;
oss << " ";
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 #ifdef _WIN64
oss << "x86_64"; oss << "x86_64";
#else #else
@ -206,6 +213,9 @@ std::string get_sysinfo()
oss << "x86"; oss << "x86";
#endif #endif
delete[] lpVersionInfo;
delete[] filePath;
return oss.str(); return oss.str();
#else #else
struct utsname osinfo; struct utsname osinfo;

View File

@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
ReflowScan::ReflowScan(Map *map, INodeDefManager *ndef) : ReflowScan::ReflowScan(Map *map, INodeDefManager *ndef) :
m_map(map), m_map(map),
m_ndef(ndef), m_ndef(ndef),
m_liquid_queue(nullptr) m_liquid_queue(NULL)
{ {
} }
@ -42,7 +42,7 @@ void ReflowScan::scan(MapBlock *block, UniqueQueue<v3s16> *liquid_queue)
// scanned block. Blocks are only added to the lookup if they are really // 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 // 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 // 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)); memset(m_lookup, 0, sizeof(m_lookup));
int block_idx = 1 + (1 * 9) + (1 * 3); int block_idx = 1 + (1 * 9) + (1 * 3);
m_lookup[block_idx] = block; m_lookup[block_idx] = block;

View File

@ -43,6 +43,7 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef):
m_last_chat_message_sent(time(NULL)), m_last_chat_message_sent(time(NULL)),
m_chat_message_allowance(5.0f), m_chat_message_allowance(5.0f),
m_message_rate_overhead(0), m_message_rate_overhead(0),
m_day_night_ratio_do_override(false),
hud_hotbar_image(""), hud_hotbar_image(""),
hud_hotbar_selected_image("") hud_hotbar_selected_image("")
{ {

View File

@ -196,6 +196,44 @@ v3f check_v3f(lua_State *L, int index)
return pos; 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) void push_ARGB8(lua_State *L, video::SColor color)
{ {
lua_newtable(L); lua_newtable(L);
@ -234,15 +272,15 @@ void push_v3s16(lua_State *L, v3s16 p)
v3s16 read_v3s16(lua_State *L, int index) v3s16 read_v3s16(lua_State *L, int index)
{ {
// Correct rounding at <0 // Correct rounding at <0
v3f pf = read_v3f(L, index); v3d pf = read_v3d(L, index);
return floatToInt(pf, 1.0); return doubleToInt(pf, 1.0);
} }
v3s16 check_v3s16(lua_State *L, int index) v3s16 check_v3s16(lua_State *L, int index)
{ {
// Correct rounding at <0 // Correct rounding at <0
v3f pf = check_v3f(L, index); v3d pf = check_v3d(L, index);
return floatToInt(pf, 1.0); return doubleToInt(pf, 1.0);
} }
bool read_color(lua_State *L, int index, video::SColor *color) bool read_color(lua_State *L, int index, video::SColor *color)

View File

@ -178,9 +178,17 @@ void log_deprecated(lua_State *L, const std::string &message)
} }
if (do_log) { if (do_log) {
warningstream << message << std::endl; warningstream << message;
// L can be NULL if we get called by log_deprecated(const std::string &msg) if (L) { // L can be NULL if we get called from scripting_game.cpp
// 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 (L) {
if (do_error) if (do_error)
script_error(L, LUA_ERRRUN, NULL, NULL); script_error(L, LUA_ERRRUN, NULL, NULL);

View File

@ -119,6 +119,9 @@ ScriptApiBase::ScriptApiBase() :
m_environment = NULL; m_environment = NULL;
m_guiengine = NULL; m_guiengine = NULL;
// Make sure Lua uses the right locale
setlocale(LC_NUMERIC, "C");
} }
ScriptApiBase::~ScriptApiBase() ScriptApiBase::~ScriptApiBase()
@ -322,6 +325,10 @@ void ScriptApiBase::objectrefGetOrCreate(lua_State *L,
ObjectRef::create(L, cobj); ObjectRef::create(L, cobj);
} else { } else {
push_objectRef(L, cobj->getId()); push_objectRef(L, cobj->getId());
if (cobj->isGone())
warningstream << "ScriptApiBase::objectrefGetOrCreate(): "
<< "Pushing ObjectRef to removed/deactivated object"
<< ", this is probably a bug." << std::endl;
} }
} }

View File

@ -69,7 +69,12 @@ bool ScriptApiItem::item_OnPlace(ItemStack &item,
// Call function // Call function
LuaItemStack::create(L, item); LuaItemStack::create(L, item);
if (!placer)
lua_pushnil(L);
else
objectrefGetOrCreate(L, placer); objectrefGetOrCreate(L, placer);
pushPointedThing(pointed); pushPointedThing(pointed);
PCALL_RES(lua_pcall(L, 3, 1, error_handler)); PCALL_RES(lua_pcall(L, 3, 1, error_handler));
if (!lua_isnil(L, -1)) { if (!lua_isnil(L, -1)) {
@ -206,7 +211,8 @@ bool ScriptApiItem::item_CraftPredict(ItemStack &item, ServerActiveObject *user,
// function onto the stack // function onto the stack
// If core.registered_items[name] doesn't exist, core.nodedef_default // If core.registered_items[name] doesn't exist, core.nodedef_default
// is tried instead so unknown items can still be manipulated to some degree // 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(); lua_State* L = getStack();
@ -217,10 +223,12 @@ bool ScriptApiItem::getItemCallback(const char *name, const char *callbackname)
lua_getfield(L, -1, name); lua_getfield(L, -1, name);
lua_remove(L, -2); // Remove registered_items lua_remove(L, -2); // Remove registered_items
// Should be a table // Should be a table
if(lua_type(L, -1) != LUA_TTABLE) if (lua_type(L, -1) != LUA_TTABLE) {
{
// Report error and clean up // 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); lua_pop(L, 1);
// Try core.nodedef_default instead // Try core.nodedef_default instead

View File

@ -53,7 +53,7 @@ protected:
friend class LuaItemStack; friend class LuaItemStack;
friend class ModApiItemMod; 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); void pushPointedThing(const PointedThing& pointed);
}; };

View File

@ -107,7 +107,7 @@ bool ScriptApiNode::node_on_punch(v3s16 p, MapNode node,
INodeDefManager *ndef = getServer()->ndef(); INodeDefManager *ndef = getServer()->ndef();
// Push callback function on stack // 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; return false;
// Call function // Call function
@ -130,7 +130,7 @@ bool ScriptApiNode::node_on_dig(v3s16 p, MapNode node,
INodeDefManager *ndef = getServer()->ndef(); INodeDefManager *ndef = getServer()->ndef();
// Push callback function on stack // 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; return false;
// Call function // Call function
@ -151,7 +151,7 @@ void ScriptApiNode::node_on_construct(v3s16 p, MapNode node)
INodeDefManager *ndef = getServer()->ndef(); INodeDefManager *ndef = getServer()->ndef();
// Push callback function on stack // 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; return;
// Call function // Call function
@ -169,7 +169,7 @@ void ScriptApiNode::node_on_destruct(v3s16 p, MapNode node)
INodeDefManager *ndef = getServer()->ndef(); INodeDefManager *ndef = getServer()->ndef();
// Push callback function on stack // 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; return;
// Call function // Call function
@ -187,7 +187,7 @@ bool ScriptApiNode::node_on_flood(v3s16 p, MapNode node, MapNode newnode)
INodeDefManager *ndef = getServer()->ndef(); INodeDefManager *ndef = getServer()->ndef();
// Push callback function on stack // 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; return false;
// Call function // Call function
@ -208,7 +208,7 @@ void ScriptApiNode::node_after_destruct(v3s16 p, MapNode node)
INodeDefManager *ndef = getServer()->ndef(); INodeDefManager *ndef = getServer()->ndef();
// Push callback function on stack // 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; return;
// Call function // Call function
@ -227,7 +227,7 @@ bool ScriptApiNode::node_on_timer(v3s16 p, MapNode node, f32 dtime)
INodeDefManager *ndef = getServer()->ndef(); INodeDefManager *ndef = getServer()->ndef();
// Push callback function on stack // 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; return false;
// Call function // Call function
@ -255,7 +255,7 @@ void ScriptApiNode::node_on_receive_fields(v3s16 p,
return; return;
// Push callback function on stack // 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; return;
// Call function // Call function

View File

@ -45,7 +45,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove(v3s16 p,
// Push callback function on stack // Push callback function on stack
std::string nodename = ndef->get(node).name; 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; return count;
// function(pos, from_list, from_index, to_list, to_index, count, player) // 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 // Push callback function on stack
std::string nodename = ndef->get(node).name; 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; return stack.count;
// Call function(pos, listname, index, stack, player) // Call function(pos, listname, index, stack, player)
@ -119,7 +119,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake(v3s16 p,
// Push callback function on stack // Push callback function on stack
std::string nodename = ndef->get(node).name; 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; return stack.count;
// Call function(pos, listname, index, count, player) // Call function(pos, listname, index, count, player)
@ -156,7 +156,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnMove(v3s16 p,
// Push callback function on stack // Push callback function on stack
std::string nodename = ndef->get(node).name; 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; return;
// function(pos, from_list, from_index, to_list, to_index, count, player) // 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 // Push callback function on stack
std::string nodename = ndef->get(node).name; 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; return;
// Call function(pos, listname, index, stack, player) // Call function(pos, listname, index, stack, player)
@ -220,7 +220,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnTake(v3s16 p,
// Push callback function on stack // Push callback function on stack
std::string nodename = ndef->get(node).name; 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; return;
// Call function(pos, listname, index, stack, player) // Call function(pos, listname, index, stack, player)

View File

@ -250,8 +250,7 @@ void ScriptApiSecurity::initializeSecurityClient()
"clock", "clock",
"date", "date",
"difftime", "difftime",
"time", "time"
"setlocale",
}; };
static const char *debug_whitelist[] = { static const char *debug_whitelist[] = {
"getinfo", "getinfo",

View File

@ -291,8 +291,7 @@ int ModApiEnvMod::l_place_node(lua_State *L)
pointed.type = POINTEDTHING_NODE; pointed.type = POINTEDTHING_NODE;
pointed.node_abovesurface = pos; pointed.node_abovesurface = pos;
pointed.node_undersurface = pos + v3s16(0,-1,0); pointed.node_undersurface = pos + v3s16(0,-1,0);
// Place it with a NULL placer (appears in Lua as a non-functional // Place it with a NULL placer (appears in Lua as nil)
// ObjectRef)
bool success = scriptIfaceItem->item_OnPlace(item, NULL, pointed); bool success = scriptIfaceItem->item_OnPlace(item, NULL, pointed);
lua_pushboolean(L, success); lua_pushboolean(L, success);
return 1; return 1;
@ -537,10 +536,12 @@ int ModApiEnvMod::l_get_objects_inside_radius(lua_State *L)
std::vector<u16>::const_iterator iter = ids.begin(); std::vector<u16>::const_iterator iter = ids.begin();
for(u32 i = 0; iter != ids.end(); ++iter) { for(u32 i = 0; iter != ids.end(); ++iter) {
ServerActiveObject *obj = env->getActiveObject(*iter); ServerActiveObject *obj = env->getActiveObject(*iter);
if (!obj->isGone()) {
// Insert object reference into table // Insert object reference into table
script->objectrefGetOrCreate(L, obj); script->objectrefGetOrCreate(L, obj);
lua_rawseti(L, -2, ++i); lua_rawseti(L, -2, ++i);
} }
}
return 1; return 1;
} }
@ -651,22 +652,31 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L)
INodeDefManager *ndef = getServer(L)->ndef(); INodeDefManager *ndef = getServer(L)->ndef();
v3s16 minp = read_v3s16(L, 1); v3s16 minp = read_v3s16(L, 1);
v3s16 maxp = read_v3s16(L, 2); 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<content_t> filter; std::set<content_t> filter;
if(lua_istable(L, 3)) { if (lua_istable(L, 3)) {
int table = 3;
lua_pushnil(L); lua_pushnil(L);
while(lua_next(L, table) != 0) { while (lua_next(L, 3) != 0) {
// key at index -2 and value at index -1 // key at index -2 and value at index -1
luaL_checktype(L, -1, LUA_TSTRING); luaL_checktype(L, -1, LUA_TSTRING);
ndef->getIds(lua_tostring(L, -1), filter); ndef->getIds(lua_tostring(L, -1), filter);
// removes value, keeps key for next iteration // removes value, keeps key for next iteration
lua_pop(L, 1); lua_pop(L, 1);
} }
} else if(lua_isstring(L, 3)) { } else if (lua_isstring(L, 3)) {
ndef->getIds(lua_tostring(L, 3), filter); ndef->getIds(lua_tostring(L, 3), filter);
} }
std::map<content_t, u16> individual_count; UNORDERED_MAP<content_t, u32> individual_count;
lua_newtable(L); lua_newtable(L);
u64 i = 0; u64 i = 0;
@ -682,7 +692,7 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L)
} }
} }
lua_newtable(L); lua_newtable(L);
for (std::set<content_t>::iterator it = filter.begin(); for (std::set<content_t>::const_iterator it = filter.begin();
it != filter.end(); ++it) { it != filter.end(); ++it) {
lua_pushnumber(L, individual_count[*it]); lua_pushnumber(L, individual_count[*it]);
lua_setfield(L, -2, ndef->get(*it).name.c_str()); 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(); INodeDefManager *ndef = getServer(L)->ndef();
v3s16 minp = read_v3s16(L, 1); v3s16 minp = read_v3s16(L, 1);
v3s16 maxp = read_v3s16(L, 2); 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<content_t> filter; std::set<content_t> filter;
if (lua_istable(L, 3)) { if (lua_istable(L, 3)) {
int table = 3;
lua_pushnil(L); lua_pushnil(L);
while(lua_next(L, table) != 0) { while (lua_next(L, 3) != 0) {
// key at index -2 and value at index -1 // key at index -2 and value at index -1
luaL_checktype(L, -1, LUA_TSTRING); luaL_checktype(L, -1, LUA_TSTRING);
ndef->getIds(lua_tostring(L, -1), filter); 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++) { for (; y <= maxp.Y; y++) {
v3s16 psurf(x, y + 1, z); v3s16 psurf(x, y + 1, z);
content_t csurf = env->getMap().getNodeNoEx(psurf).getContent(); 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) { filter.count(c) != 0) {
push_v3s16(L, v3s16(x, y, z)); push_v3s16(L, v3s16(x, y, z));
lua_rawseti(L, -2, ++i); lua_rawseti(L, -2, ++i);

View File

@ -325,8 +325,8 @@ int InvRef::l_room_for_item(lua_State *L)
return 1; return 1;
} }
// 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 // Returns true if the list contains the given count of the given item
int InvRef::l_contains_item(lua_State *L) int InvRef::l_contains_item(lua_State *L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
@ -334,8 +334,11 @@ int InvRef::l_contains_item(lua_State *L)
const char *listname = luaL_checkstring(L, 2); const char *listname = luaL_checkstring(L, 2);
ItemStack item = read_item(L, 3, getServer(L)->idef()); ItemStack item = read_item(L, 3, getServer(L)->idef());
InventoryList *list = getlist(L, ref, listname); InventoryList *list = getlist(L, ref, listname);
if(list){ bool match_meta = false;
lua_pushboolean(L, list->containsItem(item)); if (lua_isboolean(L, 4))
match_meta = lua_toboolean(L, 4);
if (list) {
lua_pushboolean(L, list->containsItem(item, match_meta));
} else { } else {
lua_pushboolean(L, false); lua_pushboolean(L, false);
} }

View File

@ -93,7 +93,7 @@ private:
// Returns true if the item completely fits into the list // Returns true if the item completely fits into the list
static int l_room_for_item(lua_State *L); 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 // Returns true if the list contains the given count of the given item name
static int l_contains_item(lua_State *L); static int l_contains_item(lua_State *L);

View File

@ -95,7 +95,7 @@ int MetaDataRef::l_get_int(lua_State *L)
MAP_LOCK_REQUIRED; MAP_LOCK_REQUIRED;
MetaDataRef *ref = checkobject(L, 1); MetaDataRef *ref = checkobject(L, 1);
std::string name = lua_tostring(L, 2); std::string name = luaL_checkstring(L, 2);
Metadata *meta = ref->getmeta(false); Metadata *meta = ref->getmeta(false);
if (meta == NULL) { if (meta == NULL) {
@ -114,8 +114,8 @@ int MetaDataRef::l_set_int(lua_State *L)
MAP_LOCK_REQUIRED; MAP_LOCK_REQUIRED;
MetaDataRef *ref = checkobject(L, 1); MetaDataRef *ref = checkobject(L, 1);
std::string name = lua_tostring(L, 2); std::string name = luaL_checkstring(L, 2);
int a = lua_tointeger(L, 3); int a = luaL_checkint(L, 3);
std::string str = itos(a); std::string str = itos(a);
Metadata *meta = ref->getmeta(true); Metadata *meta = ref->getmeta(true);
@ -133,7 +133,7 @@ int MetaDataRef::l_get_float(lua_State *L)
MAP_LOCK_REQUIRED; MAP_LOCK_REQUIRED;
MetaDataRef *ref = checkobject(L, 1); MetaDataRef *ref = checkobject(L, 1);
std::string name = lua_tostring(L, 2); std::string name = luaL_checkstring(L, 2);
Metadata *meta = ref->getmeta(false); Metadata *meta = ref->getmeta(false);
if (meta == NULL) { if (meta == NULL) {
@ -152,8 +152,8 @@ int MetaDataRef::l_set_float(lua_State *L)
MAP_LOCK_REQUIRED; MAP_LOCK_REQUIRED;
MetaDataRef *ref = checkobject(L, 1); MetaDataRef *ref = checkobject(L, 1);
std::string name = lua_tostring(L, 2); std::string name = luaL_checkstring(L, 2);
float a = lua_tonumber(L, 3); float a = luaL_checknumber(L, 3);
std::string str = ftos(a); std::string str = ftos(a);
Metadata *meta = ref->getmeta(true); Metadata *meta = ref->getmeta(true);

View File

@ -158,7 +158,7 @@ bool NodeMetaRef::handleFromTable(lua_State *L, int table, Metadata *_meta)
lua_pushnil(L); lua_pushnil(L);
while (lua_next(L, inventorytable) != 0) { while (lua_next(L, inventorytable) != 0) {
// key at index -2 and value at index -1 // 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)); read_inventory_list(L, -1, inv, name.c_str(), getServer(L));
lua_pop(L, 1); // Remove value, keep key for next iteration lua_pop(L, 1); // Remove value, keep key for next iteration
} }

View File

@ -316,7 +316,7 @@ int LuaPerlinNoiseMap::l_getMapSlice(lua_State *L)
Noise *n = o->noise; Noise *n = o->noise;
if (use_buffer) if (use_buffer)
lua_pushvalue(L, 3); lua_pushvalue(L, 4);
else else
lua_newtable(L); lua_newtable(L);

View File

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_item.h" #include "lua_api/l_item.h"
#include "common/c_converter.h" #include "common/c_converter.h"
#include "common/c_content.h" #include "common/c_content.h"
#include "util/cpp11_container.h"
#include "log.h" #include "log.h"
#include "tool.h" #include "tool.h"
#include "serverobject.h" #include "serverobject.h"
@ -138,15 +139,15 @@ int ObjectRef::l_remove(lua_State *L)
return 0; return 0;
const UNORDERED_SET<int> &child_ids = co->getAttachmentChildIds(); const UNORDERED_SET<int> &child_ids = co->getAttachmentChildIds();
UNORDERED_SET<int>::const_iterator it; for (UNORDERED_SET<int>::const_iterator it = child_ids.begin(); it != child_ids.end();
for (it = child_ids.begin(); it != child_ids.end(); ++it) { ++it) {
// Child can be NULL if it was deleted earlier // Child can be NULL if it was deleted earlier
if (ServerActiveObject *child = env->getActiveObject(*it)) if (ServerActiveObject *child = env->getActiveObject(*it))
child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
} }
verbosestream<<"ObjectRef::l_remove(): id="<<co->getId()<<std::endl; verbosestream << "ObjectRef::l_remove(): id=" << co->getId() << std::endl;
co->m_removed = true; co->m_pending_removal = true;
return 0; return 0;
} }

View File

@ -74,7 +74,7 @@ int ModApiServer::l_chat_send_all(lua_State *L)
// Get server from registry // Get server from registry
Server *server = getServer(L); Server *server = getServer(L);
// Send // Send
server->notifyPlayers(narrow_to_wide(text)); server->notifyPlayers(utf8_to_wide(text));
return 0; return 0;
} }
@ -88,7 +88,7 @@ int ModApiServer::l_chat_send_player(lua_State *L)
// Get server from registry // Get server from registry
Server *server = getServer(L); Server *server = getServer(L);
// Send // Send
server->notifyPlayer(name, narrow_to_wide(text)); server->notifyPlayer(name, utf8_to_wide(text));
return 0; return 0;
} }

View File

@ -36,6 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/base64.h" #include "util/base64.h"
#include "config.h" #include "config.h"
#include "version.h" #include "version.h"
#include "util/hex.h"
#include "util/sha1.h"
#include <algorithm> #include <algorithm>
@ -354,6 +356,23 @@ int ModApiUtil::l_get_dir_list(lua_State *L)
return 1; 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) int ModApiUtil::l_request_insecure_environment(lua_State *L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
@ -422,6 +441,32 @@ int ModApiUtil::l_get_version(lua_State *L)
return 1; 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) 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(mkdir);
API_FCT(get_dir_list); API_FCT(get_dir_list);
API_FCT(safe_file_write);
API_FCT(request_insecure_environment); API_FCT(request_insecure_environment);
@ -454,6 +500,7 @@ void ModApiUtil::Initialize(lua_State *L, int top)
API_FCT(decode_base64); API_FCT(decode_base64);
API_FCT(get_version); API_FCT(get_version);
API_FCT(sha1);
LuaSettings::create(L, g_settings, g_settings_path); LuaSettings::create(L, g_settings, g_settings_path);
lua_setfield(L, top, "settings"); lua_setfield(L, top, "settings");
@ -479,6 +526,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top)
API_FCT(decode_base64); API_FCT(decode_base64);
API_FCT(get_version); API_FCT(get_version);
API_FCT(sha1);
} }
void ModApiUtil::InitializeAsync(lua_State *L, int top) 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(decode_base64);
API_FCT(get_version); API_FCT(get_version);
API_FCT(sha1);
LuaSettings::create(L, g_settings, g_settings_path); LuaSettings::create(L, g_settings, g_settings_path);
lua_setfield(L, top, "settings"); lua_setfield(L, top, "settings");

Some files were not shown because too many files have changed in this diff Show More