diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f6d1111be..f608f3c76a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Also remember to set PROTOCOL_VERSION in clientserver.h when releasing set(VERSION_MAJOR 0) set(VERSION_MINOR 4) -set(VERSION_PATCH 1) +set(VERSION_PATCH 2-rc1) if(VERSION_EXTRA) set(VERSION_PATCH ${VERSION_PATCH}-${VERSION_EXTRA}) endif() diff --git a/builtin/chatcommands.lua b/builtin/chatcommands.lua index da9e6c78c7..9f55f1afcb 100644 --- a/builtin/chatcommands.lua +++ b/builtin/chatcommands.lua @@ -20,18 +20,13 @@ minetest.register_on_chat_message(function(name, message) end local cmd_def = minetest.chatcommands[cmd] if cmd_def then - if not cmd_def.func then - -- This is a C++ command - return false + local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs) + if has_privs then + cmd_def.func(name, param) else - local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs) - if has_privs then - cmd_def.func(name, param) - else - minetest.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")") - end - return true -- handled chat message + minetest.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")") end + return true -- handled chat message end return false end) @@ -39,17 +34,15 @@ end) -- -- Chat commands -- +minetest.register_chatcommand("me", { + params = "", + description = "chat action (eg. /me orders a pizza)", + privs = {shout=true}, + func = function(name, param) + minetest.chat_send_all("* " .. name .. " " .. param) + end, +}) --- Register C++ commands without functions -minetest.register_chatcommand("me", {params = nil, description = "chat action (eg. /me orders a pizza)", privs = {shout=true}}) -minetest.register_chatcommand("status", {description = "print server status line"}) -minetest.register_chatcommand("shutdown", {params = "", description = "shutdown server", privs = {server=true}}) -minetest.register_chatcommand("clearobjects", {params = "", description = "clear all objects in world", privs = {server=true}}) -minetest.register_chatcommand("time", {params = "<0...24000>", description = "set time of day", privs = {settime=true}}) -minetest.register_chatcommand("ban", {params = "", description = "ban IP of player", privs = {ban=true}}) -minetest.register_chatcommand("unban", {params = "", description = "remove IP ban", privs = {ban=true}}) - --- Register other commands minetest.register_chatcommand("help", { privs = {}, params = "(nothing)/all/privs/", @@ -496,3 +489,171 @@ minetest.register_chatcommand("pulverize", { end, }) +-- Key = player name +minetest.rollback_punch_callbacks = {} + +minetest.register_on_punchnode(function(pos, node, puncher) + local name = puncher:get_player_name() + if minetest.rollback_punch_callbacks[name] then + minetest.rollback_punch_callbacks[name](pos, node, puncher) + minetest.rollback_punch_callbacks[name] = nil + end +end) + +minetest.register_chatcommand("rollback_check", { + params = "[] []", + description = "check who has last touched a node or near it, ".. + "max. ago (default range=0, seconds=86400=24h)", + privs = {rollback=true}, + func = function(name, param) + local range, seconds = string.match(param, "(%d+) *(%d*)") + range = tonumber(range) or 0 + seconds = tonumber(seconds) or 86400 + minetest.chat_send_player(name, "Punch a node (limits set: range=".. + dump(range).." seconds="..dump(seconds).."s)") + minetest.rollback_punch_callbacks[name] = function(pos, node, puncher) + local name = puncher:get_player_name() + minetest.chat_send_player(name, "Checking...") + local actor, act_p, act_seconds = + minetest.rollback_get_last_node_actor(pos, range, seconds) + if actor == "" then + minetest.chat_send_player(name, "Nobody has touched the ".. + "specified location in "..dump(seconds).." seconds") + return + end + local nodedesc = "this node" + if act_p.x ~= pos.x or act_p.y ~= pos.y or act_p.z ~= pos.z then + nodedesc = minetest.pos_to_string(act_p) + end + local nodename = minetest.env:get_node(act_p).name + minetest.chat_send_player(name, "Last actor on "..nodedesc.. + " was "..actor..", "..dump(act_seconds).. + "s ago (node is now "..nodename..")") + end + end, +}) + +minetest.register_chatcommand("rollback", { + params = " [] | : []", + description = "revert actions of a player; default for is 60", + privs = {rollback=true}, + func = function(name, param) + local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)") + if not target_name then + local player_name = nil; + player_name, seconds = string.match(param, "([^ ]+) *(%d*)") + if not player_name then + minetest.chat_send_player(name, "Invalid parameters. See /help rollback and /help rollback_check") + return + end + target_name = "player:"..player_name + end + seconds = tonumber(seconds) or 60 + minetest.chat_send_player(name, "Reverting actions of ".. + dump(target_name).." since "..dump(seconds).." seconds.") + local success, log = minetest.rollback_revert_actions_by( + target_name, seconds) + if #log > 10 then + minetest.chat_send_player(name, "(log is too long to show)") + else + for _,line in ipairs(log) do + minetest.chat_send_player(name, line) + end + end + if success then + minetest.chat_send_player(name, "Reverting actions succeeded.") + else + minetest.chat_send_player(name, "Reverting actions FAILED.") + end + end, +}) + +minetest.register_chatcommand("status", { + params = "", + description = "print server status line", + privs = {}, + func = function(name, param) + minetest.chat_send_player(name, minetest.get_server_status()) + end, +}) + +minetest.register_chatcommand("time", { + params = "<0...24000>", + description = "set time of day", + privs = {settime=true}, + func = function(name, param) + if param == "" then + minetest.chat_send_player(name, "Missing parameter") + return + end + local newtime = tonumber(param) + if newtime == nil then + minetest.chat_send_player(name, "Invalid time") + else + minetest.env:set_timeofday((newtime % 24000) / 24000) + minetest.chat_send_player(name, "Time of day changed.") + minetest.log("action", name .. " sets time " .. newtime) + end + end, +}) + +minetest.register_chatcommand("shutdown", { + params = "", + description = "shutdown server", + privs = {server=true}, + func = function(name, param) + minetest.log("action", name .. " shuts down server") + minetest.request_shutdown() + minetest.chat_send_all("*** Server shutting down (operator request).") + end, +}) + +minetest.register_chatcommand("ban", { + params = "", + description = "ban IP of player", + privs = {ban=true}, + func = function(name, param) + if param == "" then + minetest.chat_send_player(name, "Ban list: " .. minetest.get_ban_list()) + return + end + if not minetest.env:get_player_by_name(param) then + minetest.chat_send_player(name, "No such player") + return + end + if not minetest.ban_player(param) then + minetest.chat_send_player(name, "Failed to ban player") + else + local desc = minetest.get_ban_description(param) + minetest.chat_send_player(name, "Banned " .. desc .. ".") + minetest.log("action", name .. " bans " .. desc .. ".") + end + end, +}) + +minetest.register_chatcommand("unban", { + params = "", + description = "remove IP ban", + privs = {ban=true}, + func = function(name, param) + if not minetest.unban_player_or_ip(param) then + minetest.chat_send_player(name, "Failed to unban player/IP") + else + minetest.chat_send_player(name, "Unbanned " .. param) + minetest.log("action", name .. " unbans " .. param) + end + end, +}) + +minetest.register_chatcommand("clearobjects", { + params = "", + description = "clear all objects in world", + privs = {server=true}, + func = function(name, param) + minetest.log("action", name .. " clears all objects") + minetest.chat_send_all("Clearing all objects. This may take long. You may experience a timeout. (by " .. name .. ")") + minetest.env:clear_objects() + minetest.log("action", "object clearing done") + minetest.chat_send_all("*** Cleared all objects.") + end, +}) diff --git a/builtin/item.lua b/builtin/item.lua index 1b4b4d25a1..4e3c74444d 100644 --- a/builtin/item.lua +++ b/builtin/item.lua @@ -227,10 +227,12 @@ function minetest.item_drop(itemstack, dropper, pos) local v = dropper:get_look_dir() local p = {x=pos.x+v.x, y=pos.y+1.5+v.y, z=pos.z+v.z} local obj = minetest.env:add_item(p, itemstack) - v.x = v.x*2 - v.y = v.y*2 + 1 - v.z = v.z*2 - obj:setvelocity(v) + if obj then + v.x = v.x*2 + v.y = v.y*2 + 1 + v.z = v.z*2 + obj:setvelocity(v) + end else minetest.env:add_item(pos, itemstack) end @@ -262,7 +264,8 @@ function minetest.node_dig(pos, node, digger) minetest.debug("node_dig") local def = ItemStack({name=node.name}):get_definition() - if not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then + -- Check if def ~= 0 because we always want to be able to remove unknown nodes + if #def ~= 0 and not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then minetest.debug("not diggable") minetest.log("info", digger:get_player_name() .. " tried to dig " .. node.name .. " which is not diggable " diff --git a/builtin/privileges.lua b/builtin/privileges.lua index 6cb42c103c..9ec09d7f6a 100644 --- a/builtin/privileges.lua +++ b/builtin/privileges.lua @@ -44,5 +44,5 @@ minetest.register_privilege("fast", { description = "Can walk fast using the fast_move mode", give_to_singleplayer = false, }) - +minetest.register_privilege("rollback", "Can use the rollback functionality") diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 35b89021d3..d3e254a2d8 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -423,7 +423,7 @@ effective towards. Groups in crafting recipes --------------------------- -An example: +An example: Make meat soup from any meat, any water and any bowl { output = 'food:meat_soup_raw', recipe = { @@ -431,7 +431,13 @@ An example: {'group:water'}, {'group:bowl'}, }, - preserve = {'group:bowl'}, -- Not implemented yet (TODO) + -- preserve = {'group:bowl'}, -- Not implemented yet (TODO) +} +An another example: Make red wool from white wool and red dye +{ + type = 'shapeless', + output = 'wool:red', + recipe = {'wool:white', 'group:dye,basecolor_red'}, } Special groups @@ -447,6 +453,9 @@ Special groups - 2: node is removed without tool wear after 0.5 seconds or so (rail, sign) - 3: node is removed without tool wear immediately (torch) +- disable_jump: Player (and possibly other things) cannot jump from node +- fall_damage_add_percent: damage speed = speed * (1 + value/100) +- bouncy: value is bounce speed in percent Known damage and digging time defining groups ---------------------------------------------- @@ -883,6 +892,14 @@ minetest.get_craft_recipe(output) -> input stack 5, stack 6, stack 7, stack 8, stack 9 } ^ input.items = nil if no recipe found +Rollbacks: +minetest.rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds +^ Find who has done something to a node, or near a node +^ actor: "player:", also "liquid". +minetest.rollback_revert_actions_by(actor, seconds) -> bool, log messages +^ Revert latest actions of someone +^ actor: "player:", also "liquid". + Defaults for the on_* item definition functions: (These return the leftover itemstack) minetest.item_place_node(itemstack, placer, pointed_thing) @@ -914,6 +931,16 @@ minetest.after(time, func, param) ^ Call function after time seconds ^ param is optional; to pass multiple parameters, pass a table. +Server: +minetest.request_shutdown() -> request for server shutdown +minetest.get_server_status() -> server status string + +Bans: +minetest.get_ban_list() -> ban list (same as minetest.get_ban_description("")) +minetest.get_ban_description(ip_or_name) -> ban description (string) +minetest.ban_player(name) -> ban a player +minetest.unban_player_or_ip(name) -> unban player or IP address + Random: minetest.get_connected_players() -> list of ObjectRefs minetest.hash_node_position({x=,y=,z=}) -> 48-bit integer @@ -1003,6 +1030,8 @@ methods: ^ nodenames: eg. {"ignore", "group:tree"} or "default:dirt" - get_perlin(seeddiff, octaves, persistence, scale) ^ Return world-specific perlin noise (int(worldseed)+seeddiff) +- clear_objects() + ^ clear all objects in the environments Deprecated: - add_rat(pos): Add C++ rat object (no-op) - add_firefly(pos): Add C++ firefly object (no-op) @@ -1092,6 +1121,8 @@ methods: - is_empty(listname): return true if list is empty - get_size(listname): get size of a list - set_size(listname, size): set size of a list +- get_width(listname): get width of a list +- set_width(listname, width): set width of list; currently used for crafting - get_stack(listname, i): get a copy of stack index i in list - set_stack(listname, i, stack): copy stack to index i in list - get_list(listname): return full list diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua index 5c84afaa1c..176fe899b8 100644 --- a/games/minimal/mods/default/init.lua +++ b/games/minimal/mods/default/init.lua @@ -1384,7 +1384,7 @@ minetest.register_abm({ srcstack:take_item() inv:set_stack("src", 1, srcstack) else - print("Could not insert '"..cooked.item.."'") + print("Could not insert '"..cooked.item:to_string().."'") end meta:set_string("src_time", 0) end diff --git a/minetest.conf.example b/minetest.conf.example index fdfbf201cd..3f292c01e5 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -15,7 +15,7 @@ # https://bitbucket.org/celeron55/minetest/src/tip/src/defaultsettings.cpp # # A vim command to convert most of defaultsettings.cpp to conf file format: -# :'<,'>s/\tg_settings\.setDefault("\([^"]*\)", "\([^"]*\)");.*/#\1 = \2/g +# :'<,'>s/\tsettings->setDefault("\([^"]*\)", "\([^"]*\)");.*/#\1 = \2/g # Note: Some of the settings are implemented in Lua # @@ -49,8 +49,14 @@ #keymap_freemove = KEY_KEY_K #keymap_fastmove = KEY_KEY_J #keymap_screenshot = KEY_F12 +# If true, keymap_special1 instead of keymap_sneak is used for climbing down and descending +#aux1_descends = false # Some (temporary) keys for debugging #keymap_print_debug_stacks = KEY_KEY_P +#keymap_quicktune_prev = KEY_HOME +#keymap_quicktune_next = KEY_END +#keymap_quicktune_dec = KEY_NEXT +#keymap_quicktune_inc = KEY_PRIOR # Minimum FPS # The amount of rendered stuff is dynamically set according to this @@ -160,6 +166,8 @@ #disallow_empty_password = false # If true, disable cheat prevention in multiplayer #disable_anticheat = false +# If true, actions are recorded for rollback +#enable_rollback_recording = false # Profiler data print interval. #0 = disable. #profiler_print_interval = 0 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e369b9623f..8cdaa510d8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,6 +153,8 @@ configure_file( ) set(common_SRCS + rollback_interface.cpp + rollback.cpp genericobject.cpp voxelalgorithms.cpp sound.cpp @@ -191,7 +193,6 @@ set(common_SRCS connection.cpp environment.cpp server.cpp - servercommand.cpp socket.cpp mapblock.cpp mapsector.cpp diff --git a/src/collision.cpp b/src/collision.cpp index 09a7df7c2c..143b559fa7 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -211,6 +211,8 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef, std::vector cboxes; std::vector is_unloaded; std::vector is_step_up; + std::vector bouncy_values; + std::vector node_positions; { //TimeTaker tt2("collisionMoveSimple collect boxes"); ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); @@ -228,11 +230,14 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef, for(s16 y = min_y; y <= max_y; y++) for(s16 z = min_z; z <= max_z; z++) { + v3s16 p(x,y,z); try{ // Object collides into walkable nodes - MapNode n = map->getNode(v3s16(x,y,z)); - if(gamedef->getNodeDefManager()->get(n).walkable == false) + MapNode n = map->getNode(p); + const ContentFeatures &f = gamedef->getNodeDefManager()->get(n); + if(f.walkable == false) continue; + int n_bouncy_value = itemgroup_get(f.groups, "bouncy"); std::vector nodeboxes = n.getNodeBoxes(gamedef->ndef()); for(std::vector::iterator @@ -245,21 +250,27 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef, cboxes.push_back(box); is_unloaded.push_back(false); is_step_up.push_back(false); + bouncy_values.push_back(n_bouncy_value); + node_positions.push_back(p); } } catch(InvalidPositionException &e) { // Collide with unloaded nodes - aabb3f box = getNodeBox(v3s16(x,y,z), BS); + aabb3f box = getNodeBox(p, BS); cboxes.push_back(box); is_unloaded.push_back(true); is_step_up.push_back(false); + bouncy_values.push_back(0); + node_positions.push_back(p); } } } // tt2 assert(cboxes.size() == is_unloaded.size()); assert(cboxes.size() == is_step_up.size()); + assert(cboxes.size() == bouncy_values.size()); + assert(cboxes.size() == node_positions.size()); /* Collision detection @@ -342,6 +353,10 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef, cbox.MaxEdge.Y - movingbox.MinEdge.Y, d)); + // Get bounce multiplier + bool bouncy = (bouncy_values[nearest_boxindex] >= 1); + float bounce = -(float)bouncy_values[nearest_boxindex] / 100.0; + // Move to the point of collision and reduce dtime by nearest_dtime if(nearest_dtime < 0) { @@ -361,30 +376,58 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef, pos_f += speed_f * nearest_dtime; dtime -= nearest_dtime; } + + bool is_collision = true; + if(is_unloaded[nearest_boxindex]) + is_collision = false; + + CollisionInfo info; + info.type = COLLISION_NODE; + info.node_p = node_positions[nearest_boxindex]; + info.bouncy = bouncy; + info.old_speed = speed_f; // Set the speed component that caused the collision to zero if(step_up) { // Special case: Handle stairs is_step_up[nearest_boxindex] = true; + is_collision = false; } else if(nearest_collided == 0) // X { - speed_f.X = 0; + if(fabs(speed_f.X) > BS*3) + speed_f.X *= bounce; + else + speed_f.X = 0; result.collides = true; result.collides_xz = true; } else if(nearest_collided == 1) // Y { - speed_f.Y = 0; + if(fabs(speed_f.Y) > BS*3) + speed_f.Y *= bounce; + else + speed_f.Y = 0; result.collides = true; } else if(nearest_collided == 2) // Z { - speed_f.Z = 0; + if(fabs(speed_f.Z) > BS*3) + speed_f.Z *= bounce; + else + speed_f.Z = 0; result.collides = true; result.collides_xz = true; } + + info.new_speed = speed_f; + if(info.new_speed.getDistanceFrom(info.old_speed) < 0.1*BS) + is_collision = false; + + if(is_collision){ + result.collisions.push_back(info); + } } } diff --git a/src/collision.h b/src/collision.h index 243c4b2947..52a7bbb7df 100644 --- a/src/collision.h +++ b/src/collision.h @@ -26,12 +26,35 @@ with this program; if not, write to the Free Software Foundation, Inc., class Map; class IGameDef; +enum CollisionType +{ + COLLISION_NODE +}; + +struct CollisionInfo +{ + enum CollisionType type; + v3s16 node_p; // COLLISION_NODE + bool bouncy; + v3f old_speed; + v3f new_speed; + + CollisionInfo(): + type(COLLISION_NODE), + node_p(-32768,-32768,-32768), + bouncy(false), + old_speed(0,0,0), + new_speed(0,0,0) + {} +}; + struct collisionMoveResult { bool touching_ground; bool collides; bool collides_xz; bool standing_on_unloaded; + std::vector collisions; collisionMoveResult(): touching_ground(false), @@ -72,16 +95,5 @@ bool wouldCollideWithCeiling( f32 y_increase, f32 d); -enum CollisionType -{ - COLLISION_FALL -}; - -struct CollisionInfo -{ - CollisionType t; - f32 speed; -}; - #endif diff --git a/src/content_abm.cpp b/src/content_abm.cpp index edadfe99e4..12dbeea8e9 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "mapblock.h" // For getNodeBlockPos #include "mapgen.h" // For mapgen::make_tree +#include "map.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 58ff130f2b..aa5c2d6740 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" // For IntervalLimiter #include "util/serialize.h" #include "util/mathconstants.h" +#include "map.h" class Settings; struct ToolCapabilities; diff --git a/src/content_cso.cpp b/src/content_cso.cpp index 6f7ecbe503..666f17734c 100644 --- a/src/content_cso.cpp +++ b/src/content_cso.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "gamedef.h" #include "log.h" +#include "map.h" static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, float txs, float tys, int col, int row) diff --git a/src/craftdef.cpp b/src/craftdef.cpp index ab78e7560d..b15443607e 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -23,9 +23,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include #include +#include #include "gamedef.h" #include "inventory.h" #include "util/serialize.h" +#include "strfnd.h" // Check if input matches recipe // Takes recipe groups into account @@ -38,9 +40,17 @@ static bool inputItemMatchesRecipe(const std::string &inp_name, // Group if(rec_name.substr(0,6) == "group:" && idef->isKnown(inp_name)){ - std::string rec_group = rec_name.substr(6); const struct ItemDefinition &def = idef->get(inp_name); - if(itemgroup_get(def.groups, rec_group) != 0) + Strfnd f(rec_name.substr(6)); + bool all_groups_match = true; + do{ + std::string check_group = f.next(","); + if(itemgroup_get(def.groups, check_group) == 0){ + all_groups_match = false; + break; + } + }while(!f.atend()); + if(all_groups_match) return true; } @@ -140,6 +150,8 @@ static bool craftGetBounds(const std::vector &items, unsigned int w return success; } +#if 0 +// This became useless when group support was added to shapeless recipes // Convert a list of item names to a multiset static std::multiset craftMakeMultiset(const std::vector &names) { @@ -153,6 +165,7 @@ static std::multiset craftMakeMultiset(const std::vector input_filtered; + for(std::vector::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(i->name != "") + input_filtered.push_back(i->name); + } - // Get input item multiset - std::vector inp_names = craftGetItemNames(input.items, gamedef); - std::multiset inp_names_multiset = craftMakeMultiset(inp_names); + // If there is a wrong number of items in input, no match + if(input_filtered.size() != recipe.size()){ + /*dstream<<"Number of input items ("<idef())){ + all_match = false; + break; + } + } + //dstream<<" -> match="< inp_names = craftGetItemNames(input.items, gamedef); - std::multiset inp_names_multiset = craftMakeMultiset(inp_names); + // Filter empty items out of input + std::vector input_filtered; + for(std::vector::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(i->name != "") + input_filtered.push_back(i->name); + } - // Get recipe item multiset - std::multiset rec_names_multiset; - rec_names_multiset.insert(craftGetItemName(recipe, gamedef)); - - // Recipe is matched when the multisets coincide - return inp_names_multiset == rec_names_multiset; + // If there is a wrong number of items in input, no match + if(input_filtered.size() != 1){ + /*dstream<<"Number of input items ("<name); + } - // Get recipe item multiset - std::multiset rec_names_multiset; - rec_names_multiset.insert(craftGetItemName(recipe, gamedef)); - - // Recipe is matched when the multisets coincide - return inp_names_multiset == rec_names_multiset; + // If there is a wrong number of items in input, no match + if(input_filtered.size() != 1){ + /*dstream<<"Number of input items ("<setDefault("keymap_increase_viewing_range_min", "+"); settings->setDefault("keymap_decrease_viewing_range_min", "-"); + settings->setDefault("aux1_descends", "false"); // Some (temporary) keys for debugging settings->setDefault("keymap_print_debug_stacks", "KEY_KEY_P"); settings->setDefault("keymap_quicktune_prev", "KEY_HOME"); @@ -122,6 +123,7 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_pvp", "true"); settings->setDefault("disallow_empty_password", "false"); settings->setDefault("disable_anticheat", "false"); + settings->setDefault("enable_rollback_recording", "false"); settings->setDefault("profiler_print_interval", "0"); settings->setDefault("enable_mapgen_debug_info", "false"); diff --git a/src/environment.cpp b/src/environment.cpp index 50cb2c3d35..8b8808dbee 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -42,6 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "localplayer.h" #endif #include "daynightratio.h" +#include "map.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -325,6 +326,7 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, lua_State *L, m_emerger(emerger), m_random_spawn_timer(3), m_send_recommended_timer(0), + m_active_block_interval_overload_skip(0), m_game_time(0), m_game_time_fraction_counter(0) { @@ -349,6 +351,17 @@ ServerEnvironment::~ServerEnvironment() } } +Map & ServerEnvironment::getMap() +{ + return *m_map; +} + +ServerMap & ServerEnvironment::getServerMap() +{ + return *m_map; +} + + void ServerEnvironment::serializePlayers(const std::string &savedir) { std::string players_path = savedir + "/players"; @@ -1074,7 +1087,8 @@ void ServerEnvironment::step(float dtime) i = elapsed_timers.begin(); i != elapsed_timers.end(); i++){ n = block->getNodeNoEx(i->first); - if(scriptapi_node_on_timer(m_lua,i->first,n,i->second.elapsed)) + p = i->first + block->getPosRelative(); + if(scriptapi_node_on_timer(m_lua,p,n,i->second.elapsed)) block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0)); } } @@ -1083,7 +1097,12 @@ void ServerEnvironment::step(float dtime) const float abm_interval = 1.0; if(m_active_block_modifier_interval.step(dtime, abm_interval)) - { + do{ // breakable + if(m_active_block_interval_overload_skip > 0){ + ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips"); + m_active_block_interval_overload_skip--; + break; + } ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg /1s", SPT_AVG); TimeTaker timer("modify in active blocks"); @@ -1116,8 +1135,9 @@ void ServerEnvironment::step(float dtime) infostream<<"WARNING: active block modifiers took " <= 0) + continue; + // Get rid of other components + speed_diff.X = 0; + speed_diff.Z = 0; + f32 pre_factor = 1; // 1 hp per node/s + f32 tolerance = BS*14; // 5 without damage + f32 post_factor = 1; // 1 hp per node/s + if(info.type == COLLISION_NODE) { - //f32 tolerance = BS*10; // 2 without damage - //f32 tolerance = BS*12; // 3 without damage - f32 tolerance = BS*14; // 5 without damage - f32 factor = 1; - if(info.speed > tolerance) - { - f32 damage_f = (info.speed - tolerance)/BS*factor; - u16 damage = (u16)(damage_f+0.5); - if(damage != 0) - damageLocalPlayer(damage, true); - } + const ContentFeatures &f = m_gamedef->ndef()-> + get(m_map->getNodeNoEx(info.node_p)); + // Determine fall damage multiplier + int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); + pre_factor = 1.0 + (float)addp/100.0; + } + float speed = pre_factor * speed_diff.getLength(); + if(speed > tolerance) + { + f32 damage_f = (speed - tolerance)/BS * post_factor; + u16 damage = (u16)(damage_f+0.5); + if(damage != 0) + damageLocalPlayer(damage, true); } } diff --git a/src/environment.h b/src/environment.h index 0e4b85e06e..bb1da2461e 100644 --- a/src/environment.h +++ b/src/environment.h @@ -33,11 +33,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "irrlichttypes_extrabloated.h" #include "player.h" -#include "map.h" #include #include "activeobject.h" #include "util/container.h" #include "util/numeric.h" +#include "mapnode.h" +#include "mapblock.h" class Server; class ServerEnvironment; @@ -46,6 +47,8 @@ class ServerActiveObject; typedef struct lua_State lua_State; class ITextureSource; class IGameDef; +class Map; +class ServerMap; class ClientMap; class Environment @@ -191,11 +194,9 @@ public: IBackgroundBlockEmerger *emerger); ~ServerEnvironment(); - Map & getMap() - { return *m_map; } + Map & getMap(); - ServerMap & getServerMap() - { return *m_map; } + ServerMap & getServerMap(); lua_State* getLua() { return m_lua; } @@ -359,6 +360,7 @@ private: IntervalLimiter m_active_blocks_management_interval; IntervalLimiter m_active_block_modifier_interval; IntervalLimiter m_active_blocks_nodemetadata_interval; + int m_active_block_interval_overload_skip; // Time from the beginning of the game in seconds. // Incremented in step(). u32 m_game_time; diff --git a/src/gamedef.h b/src/gamedef.h index 88c15be785..87918d726c 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -29,6 +29,7 @@ class ICraftDefManager; class ITextureSource; class ISoundManager; class MtEventManager; +class IRollbackReportSink; /* An interface for fetching game-global definitions like tool and @@ -54,6 +55,10 @@ public: // Only usable on the client virtual ISoundManager* getSoundManager()=0; virtual MtEventManager* getEventManager()=0; + + // Only usable on the server, and NOT thread-safe. It is usable from the + // environment thread. + virtual IRollbackReportSink* getRollbackReportSink(){return NULL;} // Used on the client virtual bool checkLocalPrivilege(const std::string &priv) @@ -66,6 +71,7 @@ public: ITextureSource* tsrc(){return getTextureSource();} ISoundManager* sound(){return getSoundManager();} MtEventManager* event(){return getEventManager();} + IRollbackReportSink* rollback(){return getRollbackReportSink();} }; #endif diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index db29fb376d..49b292df40 100644 --- a/src/guiKeyChangeMenu.cpp +++ b/src/guiKeyChangeMenu.cpp @@ -30,6 +30,9 @@ #include #include #include "settings.h" +#include + +#define KMaxButtonPerColumns 12 enum { @@ -54,11 +57,15 @@ enum }; GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) : - GUIModalMenu(env, parent, id, menumgr) + gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) : +GUIModalMenu(env, parent, id, menumgr) { + shift_down = false; activeKey = -1; + this->key_used_text = NULL; init_keys(); + for(size_t i=0; ikey_used.push_back(key_settings.at(i)->key); } GUIKeyChangeMenu::~GUIKeyChangeMenu() @@ -71,12 +78,12 @@ void GUIKeyChangeMenu::removeChildren() const core::list &children = getChildren(); core::list children_copy; for (core::list::ConstIterator i = children.begin(); i - != children.end(); i++) + != children.end(); i++) { children_copy.push_back(*i); } for (core::list::Iterator i = children_copy.begin(); i - != children_copy.end(); i++) + != children_copy.end(); i++) { (*i)->remove(); } @@ -84,20 +91,12 @@ void GUIKeyChangeMenu::removeChildren() void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { - /* - Remove stuff - */ removeChildren(); - - /* - Calculate new sizes and positions - */ - v2s32 size(620, 430); - + core::rect < s32 > rect(screensize.X / 2 - size.X / 2, - screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2, - screensize.Y / 2 + size.Y / 2); + screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2, + screensize.Y / 2 + size.Y / 2); DesiredRect = rect; recalculateAbsolutePosition(false); @@ -109,271 +108,48 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) rect += topleft + v2s32(25, 3); //gui::IGUIStaticText *t = Environment->addStaticText(wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)"), - rect, false, true, this, -1); + rect, false, true, this, -1); //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); } + + // Build buttons + v2s32 offset(25, 60); - // buttons + for(size_t i = 0; i < key_settings.size(); i++) { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Forward"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); + key_setting *k = key_settings.at(i); + { + core::rect < s32 > rect(0, 0, 100, 20); + rect += topleft + v2s32(offset.X, offset.Y); + Environment->addStaticText(k->button_name, rect, false, true, this, -1); + } + + { + core::rect < s32 > rect(0, 0, 100, 30); + rect += topleft + v2s32(offset.X + 105, offset.Y - 5); + k->button = Environment->addButton(rect, this, k->id, wgettext(k->key.name())); + } + if(i + 1 == KMaxButtonPerColumns) + offset = v2s32(250, 60); + else + offset += v2s32(0, 25); } - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->forward = Environment->addButton(rect, this, - GUI_ID_KEY_FORWARD_BUTTON, - wgettext(key_forward.name())); - } - - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Backward"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->backward = Environment->addButton(rect, this, - GUI_ID_KEY_BACKWARD_BUTTON, - wgettext(key_backward.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Left"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->left = Environment->addButton(rect, this, GUI_ID_KEY_LEFT_BUTTON, - wgettext(key_left.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Right"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->right = Environment->addButton(rect, this, - GUI_ID_KEY_RIGHT_BUTTON, - wgettext(key_right.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Use"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->use = Environment->addButton(rect, this, GUI_ID_KEY_USE_BUTTON, - wgettext(key_use.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Sneak"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->sneak = Environment->addButton(rect, this, - GUI_ID_KEY_SNEAK_BUTTON, - wgettext(key_sneak.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Jump"), rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->jump = Environment->addButton(rect, this, GUI_ID_KEY_JUMP_BUTTON, - wgettext(key_jump.name())); - } - - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Drop"), rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->dropbtn = Environment->addButton(rect, this, GUI_ID_KEY_DROP_BUTTON, - wgettext(key_drop.name())); - } - - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Inventory"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->inventory = Environment->addButton(rect, this, - GUI_ID_KEY_INVENTORY_BUTTON, - wgettext(key_inventory.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Chat"), rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->chat = Environment->addButton(rect, this, GUI_ID_KEY_CHAT_BUTTON, - wgettext(key_chat.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Command"), rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->cmd = Environment->addButton(rect, this, GUI_ID_KEY_CMD_BUTTON, - wgettext(key_cmd.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Console"), rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->console = Environment->addButton(rect, this, GUI_ID_KEY_CONSOLE_BUTTON, - wgettext(key_console.name())); - } - - //next col - offset = v2s32(250, 60); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Toggle fly"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->fly = Environment->addButton(rect, this, GUI_ID_KEY_FLY_BUTTON, - wgettext(key_fly.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Toggle fast"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->fast = Environment->addButton(rect, this, GUI_ID_KEY_FAST_BUTTON, - wgettext(key_fast.name())); - } - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Range select"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->range = Environment->addButton(rect, this, - GUI_ID_KEY_RANGE_BUTTON, - wgettext(key_range.name())); - } - - offset += v2s32(0, 25); - { - core::rect < s32 > rect(0, 0, 100, 20); - rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(wgettext("Print stacks"), - rect, false, true, this, -1); - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); - } - - { - core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); - this->dump = Environment->addButton(rect, this, GUI_ID_KEY_DUMP_BUTTON, - wgettext(key_dump.name())); - } { core::rect < s32 > rect(0, 0, 100, 30); rect += topleft + v2s32(size.X - 100 - 20, size.Y - 40); Environment->addButton(rect, this, GUI_ID_BACK_BUTTON, - wgettext("Save")); + wgettext("Save")); } { core::rect < s32 > rect(0, 0, 100, 30); rect += topleft + v2s32(size.X - 100 - 20 - 100 - 20, size.Y - 40); Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON, - wgettext("Cancel")); + wgettext("Cancel")); } changeCtype("C"); + } void GUIKeyChangeMenu::drawMenu() @@ -396,102 +172,27 @@ void GUIKeyChangeMenu::drawMenu() bool GUIKeyChangeMenu::acceptInput() { - g_settings->set("keymap_forward", key_forward.sym()); - g_settings->set("keymap_backward", key_backward.sym()); - g_settings->set("keymap_left", key_left.sym()); - g_settings->set("keymap_right", key_right.sym()); - g_settings->set("keymap_jump", key_jump.sym()); - g_settings->set("keymap_sneak", key_sneak.sym()); - g_settings->set("keymap_drop", key_drop.sym()); - g_settings->set("keymap_inventory", key_inventory.sym()); - g_settings->set("keymap_chat", key_chat.sym()); - g_settings->set("keymap_cmd", key_cmd.sym()); - g_settings->set("keymap_console", key_console.sym()); - g_settings->set("keymap_rangeselect", key_range.sym()); - g_settings->set("keymap_freemove", key_fly.sym()); - g_settings->set("keymap_fastmove", key_fast.sym()); - g_settings->set("keymap_special1", key_use.sym()); - g_settings->set("keymap_print_debug_stacks", key_dump.sym()); + for(size_t i = 0; i < key_settings.size(); i++) + { + key_setting *k = key_settings.at(i); + g_settings->set(k->setting_name, k->key.sym()); + } clearKeyCache(); return true; } -void GUIKeyChangeMenu::init_keys() -{ - key_forward = getKeySetting("keymap_forward"); - key_backward = getKeySetting("keymap_backward"); - key_left = getKeySetting("keymap_left"); - key_right = getKeySetting("keymap_right"); - key_jump = getKeySetting("keymap_jump"); - key_sneak = getKeySetting("keymap_sneak"); - key_drop = getKeySetting("keymap_drop"); - key_inventory = getKeySetting("keymap_inventory"); - key_chat = getKeySetting("keymap_chat"); - key_cmd = getKeySetting("keymap_cmd"); - key_console = getKeySetting("keymap_console"); - key_range = getKeySetting("keymap_rangeselect"); - key_fly = getKeySetting("keymap_freemove"); - key_fast = getKeySetting("keymap_fastmove"); - key_use = getKeySetting("keymap_special1"); - key_dump = getKeySetting("keymap_print_debug_stacks"); -} bool GUIKeyChangeMenu::resetMenu() { if (activeKey >= 0) { - switch (activeKey) + for(size_t i = 0; i < key_settings.size(); i++) { - case GUI_ID_KEY_FORWARD_BUTTON: - this->forward->setText( - wgettext(key_forward.name())); - break; - case GUI_ID_KEY_BACKWARD_BUTTON: - this->backward->setText( - wgettext(key_backward.name())); - break; - case GUI_ID_KEY_LEFT_BUTTON: - this->left->setText(wgettext(key_left.name())); - break; - case GUI_ID_KEY_RIGHT_BUTTON: - this->right->setText(wgettext(key_right.name())); - break; - case GUI_ID_KEY_JUMP_BUTTON: - this->jump->setText(wgettext(key_jump.name())); - break; - case GUI_ID_KEY_SNEAK_BUTTON: - this->sneak->setText(wgettext(key_sneak.name())); - break; - case GUI_ID_KEY_DROP_BUTTON: - this->dropbtn->setText(wgettext(key_drop.name())); - break; - case GUI_ID_KEY_INVENTORY_BUTTON: - this->inventory->setText( - wgettext(key_inventory.name())); - break; - case GUI_ID_KEY_CHAT_BUTTON: - this->chat->setText(wgettext(key_chat.name())); - break; - case GUI_ID_KEY_CMD_BUTTON: - this->cmd->setText(wgettext(key_cmd.name())); - break; - case GUI_ID_KEY_CONSOLE_BUTTON: - this->console->setText(wgettext(key_console.name())); - break; - case GUI_ID_KEY_RANGE_BUTTON: - this->range->setText(wgettext(key_range.name())); - break; - case GUI_ID_KEY_FLY_BUTTON: - this->fly->setText(wgettext(key_fly.name())); - break; - case GUI_ID_KEY_FAST_BUTTON: - this->fast->setText(wgettext(key_fast.name())); - break; - case GUI_ID_KEY_USE_BUTTON: - this->use->setText(wgettext(key_use.name())); - break; - case GUI_ID_KEY_DUMP_BUTTON: - this->dump->setText(wgettext(key_dump.name())); - break; + key_setting *k = key_settings.at(i); + if(k->id == activeKey) + { + k->button->setText(wgettext(k->key.name())); + break; + } } activeKey = -1; return false; @@ -501,104 +202,72 @@ bool GUIKeyChangeMenu::resetMenu() bool GUIKeyChangeMenu::OnEvent(const SEvent& event) { if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0 - && event.KeyInput.PressedDown) + && event.KeyInput.PressedDown) { changeCtype(""); - KeyPress kp(event.KeyInput); + bool prefer_character = shift_down; + KeyPress kp(event.KeyInput, prefer_character); + + bool shift_went_down = false; + if(!shift_down && + (event.KeyInput.Key == irr::KEY_SHIFT || + event.KeyInput.Key == irr::KEY_LSHIFT || + event.KeyInput.Key == irr::KEY_RSHIFT)) + shift_went_down = true; - if (activeKey == GUI_ID_KEY_FORWARD_BUTTON) + // Remove Key already in use message + if(this->key_used_text) { - this->forward->setText(wgettext(kp.name())); - this->key_forward = kp; + this->key_used_text->remove(); + this->key_used_text = NULL; } - else if (activeKey == GUI_ID_KEY_BACKWARD_BUTTON) + // Display Key already in use message + if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end()) { - this->backward->setText(wgettext(kp.name())); - this->key_backward = kp; + core::rect < s32 > rect(0, 0, 600, 40); + rect += v2s32(0, 0) + v2s32(25, 30); + this->key_used_text = Environment->addStaticText(wgettext("Key already in use"), + rect, false, true, this, -1); + //infostream << "Key already in use" << std::endl; } - else if (activeKey == GUI_ID_KEY_LEFT_BUTTON) + + // But go on { - this->left->setText(wgettext(kp.name())); - this->key_left = kp; + key_setting *k=NULL; + for(size_t i = 0; i < key_settings.size(); i++) + { + if(key_settings.at(i)->id == activeKey) + { + k = key_settings.at(i); + break; + } + } + assert(k); + k->key = kp; + k->button->setText(wgettext(k->key.name())); + + this->key_used.push_back(kp); + + changeCtype("C"); + // Allow characters made with shift + if(shift_went_down){ + shift_down = true; + return false; + }else{ + activeKey = -1; + return true; + } } - else if (activeKey == GUI_ID_KEY_RIGHT_BUTTON) - { - this->right->setText(wgettext(kp.name())); - this->key_right = kp; - } - else if (activeKey == GUI_ID_KEY_JUMP_BUTTON) - { - this->jump->setText(wgettext(kp.name())); - this->key_jump = kp; - } - else if (activeKey == GUI_ID_KEY_SNEAK_BUTTON) - { - this->sneak->setText(wgettext(kp.name())); - this->key_sneak = kp; - } - else if (activeKey == GUI_ID_KEY_DROP_BUTTON) - { - this->dropbtn->setText(wgettext(kp.name())); - this->key_drop = kp; - } - else if (activeKey == GUI_ID_KEY_INVENTORY_BUTTON) - { - this->inventory->setText(wgettext(kp.name())); - this->key_inventory = kp; - } - else if (activeKey == GUI_ID_KEY_CHAT_BUTTON) - { - this->chat->setText(wgettext(kp.name())); - this->key_chat = kp; - } - else if (activeKey == GUI_ID_KEY_CMD_BUTTON) - { - this->cmd->setText(wgettext(kp.name())); - this->key_cmd = kp; - } - else if (activeKey == GUI_ID_KEY_CONSOLE_BUTTON) - { - this->console->setText(wgettext(kp.name())); - this->key_console = kp; - } - else if (activeKey == GUI_ID_KEY_RANGE_BUTTON) - { - this->range->setText(wgettext(kp.name())); - this->key_range = kp; - } - else if (activeKey == GUI_ID_KEY_FLY_BUTTON) - { - this->fly->setText(wgettext(kp.name())); - this->key_fly = kp; - } - else if (activeKey == GUI_ID_KEY_FAST_BUTTON) - { - this->fast->setText(wgettext(kp.name())); - this->key_fast = kp; - } - else if (activeKey == GUI_ID_KEY_USE_BUTTON) - { - this->use->setText(wgettext(kp.name())); - this->key_use = kp; - } - else if (activeKey == GUI_ID_KEY_DUMP_BUTTON) - { - this->dump->setText(wgettext(kp.name())); - this->key_dump = kp; - } - changeCtype("C"); - activeKey = -1; - return true; } if (event.EventType == EET_GUI_EVENT) { if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) + && isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { dstream << "GUIMainMenu: Not allowing focus change." - << std::endl; + << std::endl; // Returning true disables focus change return true; } @@ -606,106 +275,74 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) { if(event.GUIEvent.Caller->getID() != GUI_ID_BACK_BUTTON && - event.GUIEvent.Caller->getID() != GUI_ID_ABORT_BUTTON) + event.GUIEvent.Caller->getID() != GUI_ID_ABORT_BUTTON) { changeCtype(""); } switch (event.GUIEvent.Caller->getID()) { - case GUI_ID_BACK_BUTTON: //back - acceptInput(); - quitMenu(); - return true; - case GUI_ID_ABORT_BUTTON: //abort - quitMenu(); - return true; - case GUI_ID_KEY_FORWARD_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->forward->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_BACKWARD_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->backward->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_LEFT_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->left->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_RIGHT_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->right->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_USE_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->use->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_FLY_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->fly->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_FAST_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->fast->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_JUMP_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->jump->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_DROP_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->dropbtn->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_CHAT_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->chat->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_CMD_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->cmd->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_CONSOLE_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->console->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_SNEAK_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->sneak->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_INVENTORY_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->inventory->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_DUMP_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->dump->setText(wgettext("press Key")); - break; - case GUI_ID_KEY_RANGE_BUTTON: - resetMenu(); - activeKey = event.GUIEvent.Caller->getID(); - this->range->setText(wgettext("press Key")); - break; + case GUI_ID_BACK_BUTTON: //back + acceptInput(); + quitMenu(); + return true; + case GUI_ID_ABORT_BUTTON: //abort + quitMenu(); + return true; + default: + key_setting *k = NULL; + for(size_t i = 0; i < key_settings.size(); i++) + { + if(key_settings.at(i)->id == event.GUIEvent.Caller->getID()) + { + k = key_settings.at(i); + break; + } + } + assert(k); + + resetMenu(); + shift_down = false; + activeKey = event.GUIEvent.Caller->getID(); + k->button->setText(wgettext("press key")); + this->key_used.erase(std::remove(this->key_used.begin(), + this->key_used.end(), k->key), this->key_used.end()); + break; } + Environment->setFocus(this); //Buttons changeCtype("C"); - } } return Parent ? Parent->OnEvent(event) : false; } +void GUIKeyChangeMenu::add_key(int id, std::string button_name, std::string setting_name) +{ + key_setting *k = new key_setting; + k->id = id; + k->button_name = wgettext(button_name.c_str()); + k->setting_name = setting_name; + k->key = getKeySetting(k->setting_name.c_str()); + key_settings.push_back(k); +} + +void GUIKeyChangeMenu::init_keys() +{ + this->add_key(GUI_ID_KEY_FORWARD_BUTTON, "Forward", "keymap_forward"); + this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, "Backward", "keymap_backward"); + this->add_key(GUI_ID_KEY_LEFT_BUTTON, "Left", "keymap_left"); + this->add_key(GUI_ID_KEY_RIGHT_BUTTON, "Right", "keymap_right"); + this->add_key(GUI_ID_KEY_USE_BUTTON, "Use", "keymap_special1"); + this->add_key(GUI_ID_KEY_JUMP_BUTTON, "Jump", "keymap_jump"); + this->add_key(GUI_ID_KEY_SNEAK_BUTTON, "Sneak", "keymap_sneak"); + this->add_key(GUI_ID_KEY_DROP_BUTTON, "Drop", "keymap_drop"); + this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, "Inventory", "keymap_inventory"); + this->add_key(GUI_ID_KEY_CHAT_BUTTON, "Chat", "keymap_chat"); + this->add_key(GUI_ID_KEY_CMD_BUTTON, "Command", "keymap_cmd"); + this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, "Console", "keymap_console"); + this->add_key(GUI_ID_KEY_FLY_BUTTON, "Toggle fly", "keymap_freemove"); + this->add_key(GUI_ID_KEY_FAST_BUTTON, "Toggle fast", "keymap_fastmove"); + this->add_key(GUI_ID_KEY_RANGE_BUTTON, "Range select", "keymap_rangeselect"); + this->add_key(GUI_ID_KEY_DUMP_BUTTON, "Print stacks", "keymap_print_debug_stacks"); +} diff --git a/src/guiKeyChangeMenu.h b/src/guiKeyChangeMenu.h index 7c71b297dd..beb4f0b6fc 100644 --- a/src/guiKeyChangeMenu.h +++ b/src/guiKeyChangeMenu.h @@ -28,6 +28,16 @@ #include "gettext.h" #include "keycode.h" #include +#include + +typedef struct { + int id; + wchar_t *button_name; + KeyPress key; + std::string setting_name; + gui::IGUIButton *button; +} key_setting; + class GUIKeyChangeMenu: public GUIModalMenu { @@ -54,40 +64,15 @@ private: bool resetMenu(); - gui::IGUIButton *forward; - gui::IGUIButton *backward; - gui::IGUIButton *left; - gui::IGUIButton *right; - gui::IGUIButton *use; - gui::IGUIButton *sneak; - gui::IGUIButton *jump; - gui::IGUIButton *dropbtn; - gui::IGUIButton *inventory; - gui::IGUIButton *fly; - gui::IGUIButton *fast; - gui::IGUIButton *range; - gui::IGUIButton *dump; - gui::IGUIButton *chat; - gui::IGUIButton *cmd; - gui::IGUIButton *console; + void add_key(int id, std::string setting_name, std::string button_name); + bool shift_down; + s32 activeKey; - KeyPress key_forward; - KeyPress key_backward; - KeyPress key_left; - KeyPress key_right; - KeyPress key_use; - KeyPress key_sneak; - KeyPress key_jump; - KeyPress key_drop; - KeyPress key_inventory; - KeyPress key_fly; - KeyPress key_fast; - KeyPress key_range; - KeyPress key_chat; - KeyPress key_cmd; - KeyPress key_console; - KeyPress key_dump; + + std::vector key_used; + gui::IGUIStaticText *key_used_text; + std::vector key_settings; }; #endif diff --git a/src/inventory.cpp b/src/inventory.cpp index 0a0a29cd55..fda11e40fd 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -431,6 +431,7 @@ InventoryList::InventoryList(std::string name, u32 size, IItemDefManager *itemde { m_name = name; m_size = size; + m_width = 0; m_itemdef = itemdef; clearItems(); //m_dirty = false; @@ -459,6 +460,11 @@ void InventoryList::setSize(u32 newsize) m_size = newsize; } +void InventoryList::setWidth(u32 newwidth) +{ + m_width = newwidth; +} + void InventoryList::setName(const std::string &name) { m_name = name; @@ -468,6 +474,8 @@ void InventoryList::serialize(std::ostream &os) const { //os.imbue(std::locale("C")); + os<<"Width "<> m_width; + if (iss.fail()) + throw SerializationError("incorrect width property"); + } else if(name == "Item") { if(item_i > getSize() - 1) @@ -543,6 +558,7 @@ InventoryList & InventoryList::operator = (const InventoryList &other) { m_items = other.m_items; m_size = other.m_size; + m_width = other.m_width; m_name = other.m_name; m_itemdef = other.m_itemdef; //setDirty(true); @@ -560,6 +576,11 @@ u32 InventoryList::getSize() const return m_items.size(); } +u32 InventoryList::getWidth() const +{ + return m_width; +} + u32 InventoryList::getUsedSlots() const { u32 num = 0; diff --git a/src/inventory.h b/src/inventory.h index a3c5982562..5f90183d23 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -176,6 +176,7 @@ public: ~InventoryList(); void clearItems(); void setSize(u32 newsize); + void setWidth(u32 newWidth); void setName(const std::string &name); void serialize(std::ostream &os) const; void deSerialize(std::istream &is); @@ -185,6 +186,7 @@ public: const std::string &getName() const; u32 getSize() const; + u32 getWidth() const; // Count used slots u32 getUsedSlots() const; u32 getFreeSlots() const; @@ -240,7 +242,7 @@ public: private: std::vector m_items; - u32 m_size; + u32 m_size, m_width; std::string m_name; IItemDefManager *m_itemdef; }; diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 06978fbb9e..e2e5378383 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "main.h" // for g_settings #include "settings.h" #include "craftdef.h" +#include "rollback_interface.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -199,6 +200,14 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame return; } + /* + Do not handle rollback if both inventories are that of the same player + */ + bool ignore_rollback = ( + from_inv.type == InventoryLocation::PLAYER && + to_inv.type == InventoryLocation::PLAYER && + from_inv.name == to_inv.name); + /* Collect information of endpoints */ @@ -343,6 +352,41 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame <<" i="<rollback()) + { + IRollbackReportSink *rollback = gamedef->rollback(); + + // If source is not infinite, record item take + if(src_can_take_count != -1){ + RollbackAction action; + std::string loc; + { + std::ostringstream os(std::ios::binary); + from_inv.serialize(os); + loc = os.str(); + } + action.setModifyInventoryStack(loc, from_list, from_i, false, + src_item.getItemString()); + rollback->reportAction(action); + } + // If destination is not infinite, record item put + if(dst_can_put_count != -1){ + RollbackAction action; + std::string loc; + { + std::ostringstream os(std::ios::binary); + to_inv.serialize(os); + loc = os.str(); + } + action.setModifyInventoryStack(loc, to_list, to_i, true, + src_item.getItemString()); + rollback->reportAction(action); + } + } + /* Report move to endpoints */ @@ -405,7 +449,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame L, from_inv.p, from_list, from_i, src_item, player); } } - + mgr->setInventoryModified(from_inv); if(inv_from != inv_to) mgr->setInventoryModified(to_inv); @@ -488,6 +532,11 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame return; } + /* + Do not handle rollback if inventory is player's + */ + bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER); + /* Collect information of endpoints */ @@ -526,6 +575,7 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame // Drop the item ItemStack item1 = list_from->getItem(from_i); + item1.count = take_count; if(scriptapi_item_on_drop(player->getEnv()->getLua(), item1, player, player->getBasePosition() + v3f(0,1,0))) { @@ -575,6 +625,28 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame scriptapi_nodemeta_inventory_on_take( L, from_inv.p, from_list, from_i, src_item, player); } + + /* + Record rollback information + */ + if(!ignore_src_rollback && gamedef->rollback()) + { + IRollbackReportSink *rollback = gamedef->rollback(); + + // If source is not infinite, record item take + if(src_can_take_count != -1){ + RollbackAction action; + std::string loc; + { + std::ostringstream os(std::ios::binary); + from_inv.serialize(os); + loc = os.str(); + } + action.setModifyInventoryStack(loc, from_list, from_i, + false, src_item.getItemString()); + rollback->reportAction(action); + } + } } void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef) @@ -697,18 +769,16 @@ bool getCraftingResult(Inventory *inv, ItemStack& result, result.clear(); - // TODO: Allow different sizes of crafting grids - // Get the InventoryList in which we will operate InventoryList *clist = inv->getList("craft"); - if(!clist || clist->getSize() != 9) + if(!clist) return false; // Mangle crafting grid to an another format CraftInput ci; ci.method = CRAFT_METHOD_NORMAL; - ci.width = 3; - for(u16 i=0; i<9; i++) + ci.width = clist->getWidth() ? clist->getWidth() : 3; + for(u16 i=0; igetSize(); i++) ci.items.push_back(clist->getItem(i)); // Find out what is crafted and add it to result item slot @@ -721,7 +791,7 @@ bool getCraftingResult(Inventory *inv, ItemStack& result, if(found && decrementInput) { // CraftInput has been changed, apply changes in clist - for(u16 i=0; i<9; i++) + for(u16 i=0; igetSize(); i++) { clist->changeItem(i, ci.items[i]); } diff --git a/src/keycode.cpp b/src/keycode.cpp index df3ebc9e31..cdf3c6062d 100644 --- a/src/keycode.cpp +++ b/src/keycode.cpp @@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "main.h" // For g_settings #include "exceptions.h" #include "settings.h" +#include "log.h" +#include "hex.h" class UnknownKeycode : public BaseException { @@ -286,16 +288,30 @@ KeyPress::KeyPress(const char *name) m_name = name[0]; } -KeyPress::KeyPress(const irr::SEvent::SKeyInput &in) +KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character) { Key = in.Key; Char = in.Char; + + if(prefer_character){ + m_name.resize(MB_CUR_MAX+1, '\0'); + int written = wctomb(&m_name[0], Char); + if(written > 0){ + infostream<<"KeyPress: Preferring character for "<= 0 && "unexpected multibyte character"); + if(written < 0){ + std::string hexstr = hex_encode((const char*)&Char, sizeof(Char)); + errorstream<<"KeyPress: Unexpected multibyte character "<push_back(info); + if(info.new_speed.Y - info.old_speed.Y > 0.1*BS && + info.bouncy) + bouncy_jump = true; } } + if(bouncy_jump && control.jump){ + m_speed.Y += 6.5*BS; + touching_ground = false; + MtEvent *e = new SimpleTriggerEvent("PlayerJump"); + m_gamedef->event()->put(e); + } + if(!touching_ground_was && touching_ground){ MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround"); m_gamedef->event()->put(e); @@ -314,6 +322,15 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, */ m_old_node_below = floatToInt(position - v3f(0,BS/2,0), BS); m_old_node_below_type = nodemgr->get(map.getNodeNoEx(m_old_node_below)).name; + + /* + Check properties of the node on which the player is standing + */ + const ContentFeatures &f = nodemgr->get(map.getNodeNoEx(getStandingNodePos())); + // Determine if jumping is possible + m_can_jump = touching_ground; + if(itemgroup_get(f.groups, "disable_jump")) + m_can_jump = false; } void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d) @@ -359,31 +376,70 @@ void LocalPlayer::applyControl(float dtime) if(free_move && fast_move) superspeed = true; - // Auxiliary button 1 (E) - if(control.aux1) + // Old descend control + if(g_settings->getBool("aux1_descends")) { - if(free_move) + // Auxiliary button 1 (E) + if(control.aux1) { - // In free movement mode, aux1 descends - v3f speed = getSpeed(); - if(fast_move) - speed.Y = -20*BS; + if(free_move) + { + // In free movement mode, aux1 descends + v3f speed = getSpeed(); + if(fast_move) + speed.Y = -20*BS; + else + speed.Y = -walkspeed_max; + setSpeed(speed); + } + else if(is_climbing) + { + v3f speed = getSpeed(); + speed.Y = -3*BS; + setSpeed(speed); + } else - speed.Y = -walkspeed_max; - setSpeed(speed); + { + // If not free movement but fast is allowed, aux1 is + // "Turbo button" + if(fast_move) + superspeed = true; + } } - else if(is_climbing) + } + // New minecraft-like descend control + else + { + // Auxiliary button 1 (E) + if(control.aux1) { - v3f speed = getSpeed(); - speed.Y = -3*BS; - setSpeed(speed); + if(!free_move && !is_climbing) + { + // If not free movement but fast is allowed, aux1 is + // "Turbo button" + if(fast_move) + superspeed = true; + } } - else + + if(control.sneak) { - // If not free movement but fast is allowed, aux1 is - // "Turbo button" - if(fast_move) - superspeed = true; + if(free_move) + { + // In free movement mode, sneak descends + v3f speed = getSpeed(); + if(fast_move) + speed.Y = -20*BS; + else + speed.Y = -walkspeed_max; + setSpeed(speed); + } + else if(is_climbing) + { + v3f speed = getSpeed(); + speed.Y = -3*BS; + setSpeed(speed); + } } } @@ -420,7 +476,7 @@ void LocalPlayer::applyControl(float dtime) speed.Y = walkspeed_max; setSpeed(speed); } - else if(touching_ground) + else if(m_can_jump) { /* NOTE: The d value in move() affects jump height by diff --git a/src/localplayer.h b/src/localplayer.h index 9a9767d38e..fb57e6538e 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -101,6 +101,7 @@ private: std::string m_old_node_below_type; // Whether recalculation of the sneak node is needed bool m_need_to_get_new_sneak_node; + bool m_can_jump; }; #endif diff --git a/src/map.cpp b/src/map.cpp index dc1f45068c..824553b372 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "gamedef.h" #include "util/directiontables.h" +#include "rollback_interface.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -932,12 +933,12 @@ void Map::updateLighting(core::map & a_blocks, void Map::addNodeAndUpdate(v3s16 p, MapNode n, core::map &modified_blocks) { - INodeDefManager *nodemgr = m_gamedef->ndef(); + INodeDefManager *ndef = m_gamedef->ndef(); /*PrintInfo(m_dout); m_dout< light_sources; + + /* + Collect old node for rollback + */ + RollbackNode rollback_oldnode(this, p, m_gamedef); /* If there is a node at top and it doesn't have sunlight, @@ -960,7 +966,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, try{ MapNode topnode = getNode(toppos); - if(topnode.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN) + if(topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) node_under_sunlight = false; } catch(InvalidPositionException &e) @@ -980,7 +986,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, { enum LightBank bank = banks[i]; - u8 lightwas = getNode(p).getLight(bank, nodemgr); + u8 lightwas = getNode(p).getLight(bank, ndef); // Add the block of the added node to modified_blocks v3s16 blockpos = getNodeBlockPos(p); @@ -997,16 +1003,16 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, // light again into this. unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks); - n.setLight(bank, 0, nodemgr); + n.setLight(bank, 0, ndef); } /* If node lets sunlight through and is under sunlight, it has sunlight too. */ - if(node_under_sunlight && nodemgr->get(n).sunlight_propagates) + if(node_under_sunlight && ndef->get(n).sunlight_propagates) { - n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr); + n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef); } /* @@ -1028,7 +1034,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, TODO: This could be optimized by mass-unlighting instead of looping */ - if(node_under_sunlight && !nodemgr->get(n).sunlight_propagates) + if(node_under_sunlight && !ndef->get(n).sunlight_propagates) { s16 y = p.Y - 1; for(;; y--){ @@ -1044,12 +1050,12 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, break; } - if(n2.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN) + if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) { unLightNeighbors(LIGHTBANK_DAY, - n2pos, n2.getLight(LIGHTBANK_DAY, nodemgr), + n2pos, n2.getLight(LIGHTBANK_DAY, ndef), light_sources, modified_blocks); - n2.setLight(LIGHTBANK_DAY, 0, nodemgr); + n2.setLight(LIGHTBANK_DAY, 0, ndef); setNode(n2pos, n2); } else @@ -1078,6 +1084,17 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, block->expireDayNightDiff(); } + /* + Report for rollback + */ + if(m_gamedef->rollback()) + { + RollbackNode rollback_newnode(this, p, m_gamedef); + RollbackAction action; + action.setSetNode(p, rollback_oldnode, rollback_newnode); + m_gamedef->rollback()->reportAction(action); + } + /* Add neighboring liquid nodes and the node itself if it is liquid (=water node was added) to transform queue. @@ -1099,7 +1116,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, v3s16 p2 = p + dirs[i]; MapNode n2 = getNode(p2); - if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR) + if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR) { m_transforming_liquid.push_back(p2); } @@ -1115,7 +1132,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, void Map::removeNodeAndUpdate(v3s16 p, core::map &modified_blocks) { - INodeDefManager *nodemgr = m_gamedef->ndef(); + INodeDefManager *ndef = m_gamedef->ndef(); /*PrintInfo(m_dout); m_dout<expireDayNightDiff(); } + /* + Report for rollback + */ + if(m_gamedef->rollback()) + { + RollbackNode rollback_newnode(this, p, m_gamedef); + RollbackAction action; + action.setSetNode(p, rollback_oldnode, rollback_newnode); + m_gamedef->rollback()->reportAction(action); + } + /* Add neighboring liquid nodes and this node to transform queue. (it's vital for the node itself to get updated last.) @@ -1275,7 +1308,7 @@ void Map::removeNodeAndUpdate(v3s16 p, v3s16 p2 = p + dirs[i]; MapNode n2 = getNode(p2); - if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR) + if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR) { m_transforming_liquid.push_back(p2); } @@ -1603,7 +1636,7 @@ void Map::transformLiquids(core::map & modified_blocks) while(m_transforming_liquid.size() != 0) { // This should be done here so that it is done when continue is used - if(loopcount >= initial_size * 3) + if(loopcount >= initial_size || loopcount >= 10000) break; loopcount++; @@ -1791,7 +1824,30 @@ void Map::transformLiquids(core::map & modified_blocks) n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); } n0.setContent(new_node_content); - setNode(p0, n0); + + // Find out whether there is a suspect for this action + std::string suspect; + if(m_gamedef->rollback()){ + suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1); + } + + if(!suspect.empty()){ + // Blame suspect + RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true); + // Get old node for rollback + RollbackNode rollback_oldnode(this, p0, m_gamedef); + // Set node + setNode(p0, n0); + // Report + RollbackNode rollback_newnode(this, p0, m_gamedef); + RollbackAction action; + action.setSetNode(p0, rollback_oldnode, rollback_newnode); + m_gamedef->rollback()->reportAction(action); + } else { + // Set node + setNode(p0, n0); + } + v3s16 blockpos = getNodeBlockPos(p0); MapBlock *block = getBlockNoCreateNoEx(blockpos); if(block != NULL) { diff --git a/src/map.h b/src/map.h index b561e5e72a..30cf626bb7 100644 --- a/src/map.h +++ b/src/map.h @@ -44,6 +44,7 @@ class ServerMapSector; class MapBlock; class NodeMetadata; class IGameDef; +class IRollbackReportSink; namespace mapgen{ struct BlockMakeData; @@ -169,7 +170,7 @@ public: void removeEventReceiver(MapEventReceiver *event_receiver); // event shall be deleted by caller after the call. void dispatchEvent(MapEditEvent *event); - + // On failure returns NULL MapSector * getSectorNoGenerateNoExNoLock(v2s16 p2d); // Same as the above (there exists no lock anymore) @@ -336,7 +337,7 @@ protected: IGameDef *m_gamedef; core::map m_event_receivers; - + core::map m_sectors; // Be sure to set this to NULL when the cached sector is deleted diff --git a/src/mapgen.cpp b/src/mapgen.cpp index af53d0091d..67e92f4498 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -1297,7 +1297,9 @@ BiomeType get_biome(u64 seed, v2s16 p2d) double d = noise2d_perlin( 0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250, seed+9130, 3, 0.50); - if(d > 0.35) + if(d > 0.45) + return BT_DESERT; + if(d > 0.35 && (noise2d( p2d.X, p2d.Y, int(seed) ) + 1.0) > ( 0.45 - d ) * 20.0 ) return BT_DESERT; return BT_NORMAL; }; @@ -1759,8 +1761,9 @@ void make_block(BlockMakeData *data) vmanip.m_data[i] = airnode; } } else { - // Don't replace air or water or lava - if(vmanip.m_data[i].getContent() == CONTENT_AIR || + // Don't replace air or water or lava or ignore + if(vmanip.m_data[i].getContent() == CONTENT_IGNORE || + vmanip.m_data[i].getContent() == CONTENT_AIR || vmanip.m_data[i].getContent() == c_water_source || vmanip.m_data[i].getContent() == c_lava_source) continue; diff --git a/src/nodedef.cpp b/src/nodedef.cpp index e38061e30d..d644dc229d 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -556,8 +556,6 @@ public: f->solidness = 0; } else { f->solidness = 1; - if(f->alpha == 255) - f->solidness = 2; f->backface_culling = false; } break; diff --git a/src/player.cpp b/src/player.cpp index 2e084b415a..36f12c77b5 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -45,7 +45,8 @@ Player::Player(IGameDef *gamedef): updateName(""); inventory.clear(); inventory.addList("main", PLAYER_INVENTORY_SIZE); - inventory.addList("craft", 9); + InventoryList *craft = inventory.addList("craft", 9); + craft->setWidth(3); inventory.addList("craftpreview", 1); inventory.addList("craftresult", 1); diff --git a/src/rollback.cpp b/src/rollback.cpp new file mode 100644 index 0000000000..f0e3c40aa5 --- /dev/null +++ b/src/rollback.cpp @@ -0,0 +1,365 @@ +/* +Minetest-c55 +Copyright (C) 2012 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "rollback.h" +#include +#include +#include +#include "log.h" +#include "mapnode.h" +#include "gamedef.h" +#include "nodedef.h" +#include "util/serialize.h" +#include "util/string.h" +#include "strfnd.h" +#include "util/numeric.h" +#include "inventorymanager.h" // deserializing InventoryLocations + +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + +#define POINTS_PER_NODE (16.0) + +// Get nearness factor for subject's action for this action +// Return value: 0 = impossible, >0 = factor +static float getSuspectNearness(bool is_guess, v3s16 suspect_p, int suspect_t, + v3s16 action_p, int action_t) +{ + // Suspect cannot cause things in the past + if(action_t < suspect_t) + return 0; // 0 = cannot be + // Start from 100 + int f = 100; + // Distance (1 node = -x points) + f -= POINTS_PER_NODE * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); + // Time (1 second = -x points) + f -= 1 * (action_t - suspect_t); + // If is a guess, halve the points + if(is_guess) + f *= 0.5; + // Limit to 0 + if(f < 0) + f = 0; + return f; +} + +class RollbackManager: public IRollbackManager +{ +public: + // IRollbackManager interface + + void reportAction(const RollbackAction &action_) + { + // Ignore if not important + if(!action_.isImportant(m_gamedef)) + return; + RollbackAction action = action_; + action.unix_time = time(0); + // Figure out actor + action.actor = m_current_actor; + action.actor_is_guess = m_current_actor_is_guess; + // If actor is not known, find out suspect or cancel + if(action.actor.empty()){ + v3s16 p; + if(!action.getPosition(&p)) + return; + action.actor = getSuspect(p, 83, 1); + if(action.actor.empty()) + return; + action.actor_is_guess = true; + } + infostream<<"RollbackManager::reportAction():" + <<" time="<::const_reverse_iterator + i = m_action_latest_buffer.rbegin(); + i != m_action_latest_buffer.rend(); i++) + { + if(i->unix_time < first_time) + break; + if(i->actor == "") + continue; + // Find position of suspect or continue + v3s16 suspect_p; + if(!i->getPosition(&suspect_p)) + continue; + float f = getSuspectNearness(i->actor_is_guess, suspect_p, + i->unix_time, p, cur_time); + if(f >= min_nearness && f > likely_suspect_nearness){ + likely_suspect_nearness = f; + likely_suspect = *i; + if(likely_suspect_nearness >= nearness_shortcut) + break; + } + } + // No likely suspect was found + if(likely_suspect_nearness == 0) + return ""; + // Likely suspect was found + return likely_suspect.actor; + } + void flush() + { + infostream<<"RollbackManager::flush()"<::const_iterator + i = m_action_todisk_buffer.begin(); + i != m_action_todisk_buffer.end(); i++) + { + // Do not save stuff that does not have an actor + if(i->actor == "") + continue; + of<unix_time; + of<<" "; + of<actor); + of<<" "; + of<toString(); + if(i->actor_is_guess){ + of<<" "; + of<<"actor_is_guess"; + } + of<= 100) + flush(); + } + + bool readFile(std::list &dst) + { + // Load whole file to memory + std::ifstream f(m_filepath.c_str(), std::ios::in); + if(!f.good()){ + errorstream<<"RollbackManager::readFile(): Could not open " + <<"file for reading: \""< getEntriesSince(int first_time) + { + infostream<<"RollbackManager::getEntriesSince("< action_buffer; + // Use the latest buffer if it is long enough + if(!m_action_latest_buffer.empty() && + m_action_latest_buffer.begin()->unix_time <= first_time){ + action_buffer = m_action_latest_buffer; + } + else + { + // Save all remaining stuff + flush(); + // Load whole file to memory + bool good = readFile(action_buffer); + if(!good){ + errorstream<<"RollbackManager::getEntriesSince(): Failed to" + <<" open file; using data in memory."< action_buffer = getEntriesSince(first_time); + + std::list result; + + for(std::list::const_reverse_iterator + i = action_buffer.rbegin(); + i != action_buffer.rend(); i++) + { + if(i->unix_time < first_time) + break; + + // Find position of action or continue + v3s16 action_p; + if(!i->getPosition(&action_p)) + continue; + + if(range == 0){ + if(action_p != p) + continue; + } else { + if(abs(action_p.X - p.X) > range || + abs(action_p.Y - p.Y) > range || + abs(action_p.Z - p.Z) > range) + continue; + } + + if(act_p) + *act_p = action_p; + if(act_seconds) + *act_seconds = cur_time - i->unix_time; + return i->actor; + } + return ""; + } + + std::list getRevertActions(const std::string &actor_filter, + int seconds) + { + infostream<<"RollbackManager::getRevertActions("< action_buffer = getEntriesSince(first_time); + + std::list result; + + for(std::list::const_reverse_iterator + i = action_buffer.rbegin(); + i != action_buffer.rend(); i++) + { + if(i->unix_time < first_time) + break; + if(i->actor != actor_filter) + continue; + const RollbackAction &action = *i; + /*infostream<<"RollbackManager::revertAction(): Should revert" + <<" time="< + +class IGameDef; + +class IRollbackManager: public IRollbackReportSink +{ +public: + // IRollbackReportManager + virtual void reportAction(const RollbackAction &action) = 0; + virtual std::string getActor() = 0; + virtual bool isActorGuess() = 0; + virtual void setActor(const std::string &actor, bool is_guess) = 0; + virtual std::string getSuspect(v3s16 p, float nearness_shortcut, + float min_nearness) = 0; + + virtual ~IRollbackManager(){} + virtual void flush() = 0; + // Get last actor that did something to position p, but not further than + // in history + virtual std::string getLastNodeActor(v3s16 p, int range, int seconds, + v3s16 *act_p, int *act_seconds) = 0; + // Get actions to revert of history made by + virtual std::list getRevertActions(const std::string &actor, + int seconds) = 0; +}; + +IRollbackManager *createRollbackManager(const std::string &filepath, IGameDef *gamedef); + +#endif diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp new file mode 100644 index 0000000000..b2eb2093c5 --- /dev/null +++ b/src/rollback_interface.cpp @@ -0,0 +1,416 @@ +/* +Minetest-c55 +Copyright (C) 2012 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "rollback_interface.h" +#include +#include "util/serialize.h" +#include "util/string.h" +#include "util/numeric.h" +#include "map.h" +#include "gamedef.h" +#include "nodedef.h" +#include "nodemetadata.h" +#include "exceptions.h" +#include "log.h" +#include "inventorymanager.h" +#include "inventory.h" +#include "mapblock.h" + +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + +RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef) +{ + INodeDefManager *ndef = gamedef->ndef(); + MapNode n = map->getNodeNoEx(p); + name = ndef->get(n).name; + param1 = n.param1; + param2 = n.param2; + NodeMetadata *metap = map->getNodeMetadata(p); + if(metap){ + std::ostringstream os(std::ios::binary); + metap->serialize(os); + meta = os.str(); + } +} + +std::string RollbackAction::toString() const +{ + switch(type){ + case TYPE_SET_NODE: { + std::ostringstream os(std::ios::binary); + os<<"[set_node"; + os<<" "; + os<<"("<ndef(); + // Both are of the same name, so a single definition is needed + const ContentFeatures &def = ndef->get(n_old.name); + // If the type is flowing liquid, action is not important + if(def.liquid_type == LIQUID_FLOWING) + return false; + // Otherwise action is important + return true; } + default: + return true; + } +} + +bool RollbackAction::getPosition(v3s16 *dst) const +{ + switch(type){ + case RollbackAction::TYPE_SET_NODE: + if(dst) *dst = p; + return true; + case RollbackAction::TYPE_MODIFY_INVENTORY_STACK: { + InventoryLocation loc; + loc.deSerialize(inventory_location); + if(loc.type != InventoryLocation::NODEMETA) + return false; + if(dst) *dst = loc.p; + return true; } + default: + return false; + } +} + +bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const +{ + try{ + switch(type){ + case TYPE_NOTHING: + return true; + case TYPE_SET_NODE: { + INodeDefManager *ndef = gamedef->ndef(); + // Make sure position is loaded from disk + map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false); + // Check current node + MapNode current_node = map->getNodeNoEx(p); + std::string current_name = ndef->get(current_node).name; + // If current node not the new node, it's bad + if(current_name != n_new.name) + return false; + /*// If current node not the new node and not ignore, it's bad + if(current_name != n_new.name && current_name != "ignore") + return false;*/ + // Create rollback node + MapNode n(ndef, n_old.name, n_old.param1, n_old.param2); + // Set rollback node + try{ + if(!map->addNodeWithEvent(p, n)){ + infostream<<"RollbackAction::applyRevert(): " + <<"AddNodeWithEvent failed at " + <getNodeMetadata(p); + if(n_old.meta != ""){ + if(!meta){ + meta = new NodeMetadata(gamedef); + map->setNodeMetadata(p, meta); + } + std::istringstream is(n_old.meta, std::ios::binary); + meta->deSerialize(is); + } else { + map->removeNodeMetadata(p); + } + // NOTE: This same code is in scriptapi.cpp + // Inform other things that the metadata has changed + v3s16 blockpos = getContainerPos(p, MAP_BLOCKSIZE); + MapEditEvent event; + event.type = MEET_BLOCK_NODE_METADATA_CHANGED; + event.p = blockpos; + map->dispatchEvent(&event); + // Set the block to be saved + MapBlock *block = map->getBlockNoCreateNoEx(blockpos); + if(block) + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "NodeMetaRef::reportMetadataChange"); + }catch(InvalidPositionException &e){ + infostream<<"RollbackAction::applyRevert(): " + <<"InvalidPositionException: "<idef()); + Inventory *inv = imgr->getInventory(loc); + if(!inv){ + infostream<<"RollbackAction::applyRevert(): Could not get " + "inventory at "<getList(inventory_list); + if(!list){ + infostream<<"RollbackAction::applyRevert(): Could not get " + "inventory list \""<getSize() <= inventory_index){ + infostream<<"RollbackAction::applyRevert(): List index " + <getItem(inventory_index).name != stack.name) + return false; + list->takeItem(inventory_index, stack.count); + } else { + list->addItem(inventory_index, stack); + } + // Inventory was modified; send to clients + imgr->setInventoryModified(loc); + return true; } + default: + errorstream<<"RollbackAction::applyRevert(): type not handled" + < + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef ROLLBACK_INTERFACE_HEADER +#define ROLLBACK_INTERFACE_HEADER + +#include "irr_v3d.h" +#include +#include +#include "exceptions.h" + +class Map; +class IGameDef; +struct MapNode; +class InventoryManager; + +struct RollbackNode +{ + std::string name; + int param1; + int param2; + std::string meta; + + bool operator==(const RollbackNode &other) + { + return (name == other.name && param1 == other.param1 && + param2 == other.param2 && meta == other.meta); + } + bool operator!=(const RollbackNode &other) + { + return !(*this == other); + } + + RollbackNode(): + param1(0), + param2(0) + {} + + RollbackNode(Map *map, v3s16 p, IGameDef *gamedef); +}; + +struct RollbackAction +{ + enum Type{ + TYPE_NOTHING, + TYPE_SET_NODE, + TYPE_MODIFY_INVENTORY_STACK, + } type; + + int unix_time; + std::string actor; + bool actor_is_guess; + + v3s16 p; + RollbackNode n_old; + RollbackNode n_new; + + std::string inventory_location; + std::string inventory_list; + u32 inventory_index; + bool inventory_add; + std::string inventory_stack; + + RollbackAction(): + type(TYPE_NOTHING), + unix_time(0), + actor_is_guess(false) + {} + + void setSetNode(v3s16 p_, const RollbackNode &n_old_, + const RollbackNode &n_new_) + { + type = TYPE_SET_NODE; + p = p_; + n_old = n_old_; + n_new = n_new_; + } + + void setModifyInventoryStack(const std::string &inventory_location_, + const std::string &inventory_list_, int index_, + bool add_, const std::string &inventory_stack_) + { + type = TYPE_MODIFY_INVENTORY_STACK; + inventory_location = inventory_location_; + inventory_list = inventory_list_; + inventory_index = index_; + inventory_add = add_; + inventory_stack = inventory_stack_; + } + + // String should not contain newlines or nulls + std::string toString() const; + void fromStream(std::istream &is) throw(SerializationError); + + // Eg. flowing water level changes are not important + bool isImportant(IGameDef *gamedef) const; + + bool getPosition(v3s16 *dst) const; + + bool applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const; +}; + +class IRollbackReportSink +{ +public: + virtual ~IRollbackReportSink(){} + virtual void reportAction(const RollbackAction &action) = 0; + virtual std::string getActor() = 0; + virtual bool isActorGuess() = 0; + virtual void setActor(const std::string &actor, bool is_guess) = 0; + virtual std::string getSuspect(v3s16 p, float nearness_shortcut, + float min_nearness) = 0; +}; + +class RollbackScopeActor +{ +public: + RollbackScopeActor(IRollbackReportSink *sink, const std::string &actor, + bool is_guess=false): + m_sink(sink) + { + if(m_sink){ + m_actor_was = m_sink->getActor(); + m_actor_was_guess = m_sink->isActorGuess(); + m_sink->setActor(actor, is_guess); + } + } + ~RollbackScopeActor() + { + if(m_sink){ + m_sink->setActor(m_actor_was, m_actor_was_guess); + } + } +private: + IRollbackReportSink *m_sink; + std::string m_actor_was; + bool m_actor_was_guess; +}; + +#endif diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index e1d9185420..81e96aec64 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -47,6 +47,7 @@ extern "C" { #include "daynightratio.h" #include "noise.h" // PseudoRandom for LuaPseudoRandom #include "util/pointedthing.h" +#include "rollback.h" static void stackDump(lua_State *L, std::ostream &o) { @@ -1846,6 +1847,20 @@ private: return 1; } + // get_width(self, listname) + static int l_get_width(lua_State *L) + { + InvRef *ref = checkobject(L, 1); + const char *listname = luaL_checkstring(L, 2); + InventoryList *list = getlist(L, ref, listname); + if(list){ + lua_pushinteger(L, list->getWidth()); + } else { + lua_pushinteger(L, 0); + } + return 1; + } + // set_size(self, listname, size) static int l_set_size(lua_State *L) { @@ -1868,6 +1883,23 @@ private: return 0; } + // set_width(self, listname, size) + static int l_set_width(lua_State *L) + { + InvRef *ref = checkobject(L, 1); + const char *listname = luaL_checkstring(L, 2); + int newwidth = luaL_checknumber(L, 3); + Inventory *inv = getinv(L, ref); + InventoryList *list = inv->getList(listname); + if(list){ + list->setWidth(newwidth); + } else { + return 0; + } + reportInventoryChange(L, ref); + return 0; + } + // get_stack(self, listname, i) -> itemstack static int l_get_stack(lua_State *L) { @@ -2061,6 +2093,8 @@ const luaL_reg InvRef::methods[] = { method(InvRef, is_empty), method(InvRef, get_size), method(InvRef, set_size), + method(InvRef, get_width), + method(InvRef, set_width), method(InvRef, get_stack), method(InvRef, set_stack), method(InvRef, get_list), @@ -2106,6 +2140,7 @@ private: static void reportMetadataChange(NodeMetaRef *ref) { + // NOTE: This same code is in rollback_interface.cpp // Inform other things that the metadata has changed v3s16 blockpos = getNodeBlockPos(ref->m_p); MapEditEvent event; @@ -3808,6 +3843,15 @@ private: return 1; } + // EnvRef:clear_objects() + // clear all objects in the environment + static int l_clear_objects(lua_State *L) + { + EnvRef *o = checkobject(L, 1); + o->m_env->clearAllObjects(); + return 0; + } + public: EnvRef(ServerEnvironment *env): m_env(env) @@ -3889,6 +3933,7 @@ const luaL_reg EnvRef::methods[] = { method(EnvRef, find_node_near), method(EnvRef, find_nodes_in_area), method(EnvRef, get_perlin), + method(EnvRef, clear_objects), {0,0} }; @@ -3926,6 +3971,10 @@ private: min = luaL_checkinteger(L, 2); if(!lua_isnil(L, 3)) max = luaL_checkinteger(L, 3); + if(max < min){ + errorstream<<"PseudoRandom.next(): max="< 32767/5) throw LuaError(L, "PseudoRandom.next() max-min is not 32767 and is > 32768/5. This is disallowed due to the bad random distribution the implementation would otherwise make."); PseudoRandom &pseudo = o->m_pseudo; @@ -4164,6 +4213,20 @@ static int l_log(lua_State *L) return 0; } +// request_shutdown() +static int l_request_shutdown(lua_State *L) +{ + get_server(L)->requestShutdown(); + return 0; +} + +// get_server_status() +static int l_get_server_status(lua_State *L) +{ + lua_pushstring(L, wide_to_narrow(get_server(L)->getStatusString()).c_str()); + return 1; +} + // register_item_raw({lots of stuff}) static int l_register_item_raw(lua_State *L) { @@ -4554,6 +4617,56 @@ static int l_get_player_privs(lua_State *L) return 1; } +// get_ban_list() +static int l_get_ban_list(lua_State *L) +{ + lua_pushstring(L, get_server(L)->getBanDescription("").c_str()); + return 1; +} + +// get_ban_description() +static int l_get_ban_description(lua_State *L) +{ + const char * ip_or_name = luaL_checkstring(L, 1); + lua_pushstring(L, get_server(L)->getBanDescription(std::string(ip_or_name)).c_str()); + return 1; +} + +// ban_player() +static int l_ban_player(lua_State *L) +{ + const char * name = luaL_checkstring(L, 1); + Player *player = get_env(L)->getPlayer(name); + if(player == NULL) + { + lua_pushboolean(L, false); // no such player + return 1; + } + try + { + Address addr = get_server(L)->getPeerAddress(get_env(L)->getPlayer(name)->peer_id); + std::string ip_str = addr.serializeString(); + get_server(L)->setIpBanned(ip_str, name); + } + catch(con::PeerNotFoundException) // unlikely + { + dstream << __FUNCTION_NAME << ": peer was not found" << std::endl; + lua_pushboolean(L, false); // error + return 1; + } + lua_pushboolean(L, true); + return 1; +} + +// unban_player_or_ip() +static int l_unban_player_of_ip(lua_State *L) +{ + const char * ip_or_name = luaL_checkstring(L, 1); + get_server(L)->unsetIpBanned(ip_or_name); + lua_pushboolean(L, true); + return 1; +} + // get_inventory(location) static int l_get_inventory(lua_State *L) { @@ -4853,9 +4966,60 @@ static int l_get_craft_recipe(lua_State *L) return 1; } +// rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds +static int l_rollback_get_last_node_actor(lua_State *L) +{ + v3s16 p = read_v3s16(L, 1); + int range = luaL_checknumber(L, 2); + int seconds = luaL_checknumber(L, 3); + Server *server = get_server(L); + IRollbackManager *rollback = server->getRollbackManager(); + v3s16 act_p; + int act_seconds = 0; + std::string actor = rollback->getLastNodeActor(p, range, seconds, &act_p, &act_seconds); + lua_pushstring(L, actor.c_str()); + push_v3s16(L, act_p); + lua_pushnumber(L, act_seconds); + return 3; +} + +// rollback_revert_actions_by(actor, seconds) -> bool, log messages +static int l_rollback_revert_actions_by(lua_State *L) +{ + std::string actor = luaL_checkstring(L, 1); + int seconds = luaL_checknumber(L, 2); + Server *server = get_server(L); + IRollbackManager *rollback = server->getRollbackManager(); + std::list actions = rollback->getRevertActions(actor, seconds); + std::list log; + bool success = server->rollbackRevertActions(actions, &log); + // Push boolean result + lua_pushboolean(L, success); + // Get the table insert function and push the log table + lua_getglobal(L, "table"); + lua_getfield(L, -1, "insert"); + int table_insert = lua_gettop(L); + lua_newtable(L); + int table = lua_gettop(L); + for(std::list::const_iterator i = log.begin(); + i != log.end(); i++) + { + lua_pushvalue(L, table_insert); + lua_pushvalue(L, table); + lua_pushstring(L, i->c_str()); + if(lua_pcall(L, 2, 0, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + } + lua_remove(L, -2); // Remove table + lua_remove(L, -2); // Remove insert + return 2; +} + static const struct luaL_Reg minetest_f [] = { {"debug", l_debug}, {"log", l_log}, + {"request_shutdown", l_request_shutdown}, + {"get_server_status", l_get_server_status}, {"register_item_raw", l_register_item_raw}, {"register_alias_raw", l_register_alias_raw}, {"register_craft", l_register_craft}, @@ -4865,6 +5029,10 @@ static const struct luaL_Reg minetest_f [] = { {"chat_send_all", l_chat_send_all}, {"chat_send_player", l_chat_send_player}, {"get_player_privs", l_get_player_privs}, + {"get_ban_list", l_get_ban_list}, + {"get_ban_description", l_get_ban_description}, + {"ban_player", l_ban_player}, + {"unban_player_or_ip", l_unban_player_of_ip}, {"get_inventory", l_get_inventory}, {"create_detached_inventory_raw", l_create_detached_inventory_raw}, {"get_dig_params", l_get_dig_params}, @@ -4880,6 +5048,8 @@ static const struct luaL_Reg minetest_f [] = { {"notify_authentication_modified", l_notify_authentication_modified}, {"get_craft_result", l_get_craft_result}, {"get_craft_recipe", l_get_craft_recipe}, + {"rollback_get_last_node_actor", l_rollback_get_last_node_actor}, + {"rollback_revert_actions_by", l_rollback_revert_actions_by}, {NULL, NULL} }; @@ -5453,6 +5623,8 @@ void scriptapi_on_player_receive_fields(lua_State *L, // If that is nil or on error, return false and stack is unchanged // If that is a function, returns true and pushes the // function onto the stack +// If minetest.registered_items[name] doesn't exist, minetest.nodedef_default +// is tried instead so unknown items can still be manipulated to some degree static bool get_item_callback(lua_State *L, const char *name, const char *callbackname) { @@ -5465,9 +5637,15 @@ static bool get_item_callback(lua_State *L, // Should be a table if(lua_type(L, -1) != LUA_TTABLE) { + // Report error and clean up errorstream<<"Item \""<= 0) - { return; - } + + Player *player = server->m_env->getPlayer(peer_id); + // This can happen sometimes; clients and players are not in perfect sync. + if(player == NULL) + return; // Won't send anything if already sending if(m_blocks_sending.size() >= g_settings->getU16 @@ -456,10 +459,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, //TimeTaker timer("RemoteClient::GetNextBlocks"); - Player *player = server->m_env->getPlayer(peer_id); - - assert(player != NULL); - v3f playerpos = player->getPosition(); v3f playerspeed = player->getSpeed(); v3f playerspeeddir(0,0,0); @@ -934,6 +933,9 @@ Server::Server( m_env(NULL), m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this), m_banmanager(path_world+DIR_DELIM+"ipban.txt"), + m_rollback(NULL), + m_rollback_sink_enabled(true), + m_enable_rollback_recording(false), m_lua(NULL), m_itemdef(createItemDefManager()), m_nodedef(createNodeDefManager()), @@ -973,6 +975,10 @@ Server::Server( infostream<<"- config: "<getBool("enable_rollback_recording"); } } @@ -2481,6 +2492,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) return; } + // If something goes wrong, this player is to blame + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + /* Note: Always set inventory not sent, to repair cases where the client made a bad prediction. @@ -2615,6 +2630,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) message += (wchar_t)readU16(buf); } + // If something goes wrong, this player is to blame + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + // Get player name of this client std::wstring name = narrow_to_wide(player->getName()); @@ -2632,36 +2651,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) // Whether to send to other players bool send_to_others = false; - // Parse commands + // Commands are implemented in Lua, so only catch invalid + // commands that were not "eaten" and send an error back if(message[0] == L'/') { - size_t strip_size = 1; - if (message[1] == L'#') // support old-style commans - ++strip_size; - message = message.substr(strip_size); - - WStrfnd f1(message); - f1.next(L" "); // Skip over /#whatever - std::wstring paramstring = f1.next(L""); - - ServerCommandContext *ctx = new ServerCommandContext( - str_split(message, L' '), - paramstring, - this, - m_env, - player); - - std::wstring reply(processServerCommand(ctx)); - send_to_sender = ctx->flags & SEND_TO_SENDER; - send_to_others = ctx->flags & SEND_TO_OTHERS; - - if (ctx->flags & SEND_NO_PREFIX) - line += reply; + message = message.substr(1); + send_to_sender = true; + if(message.length() == 0) + line += L"-!- Empty command"; else - line += L"Server: " + reply; - - delete ctx; - + line += L"-!- Invalid command: " + str_split(message, L' ')[0]; } else { @@ -2949,6 +2948,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) return; } + /* + If something goes wrong, this player is to blame + */ + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + /* 0: start digging or punch object */ @@ -3204,8 +3209,23 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) fields[fieldname] = fieldvalue; } + // If something goes wrong, this player is to blame + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + + // Check the target node for rollback data; leave others unnoticed + RollbackNode rn_old(&m_env->getMap(), p, this); + scriptapi_node_on_receive_fields(m_lua, p, formname, fields, playersao); + + // Report rollback data + RollbackNode rn_new(&m_env->getMap(), p, this); + if(rollback() && rn_new != rn_old){ + RollbackAction action; + action.setSetNode(p, rn_old, rn_new); + rollback()->reportAction(action); + } } else if(command == TOSERVER_INVENTORY_FIELDS) { @@ -4407,9 +4427,10 @@ std::wstring Server::getStatusString() // Uptime os<::Iterator - i = m_clients.getIterator(); + for(i = m_clients.getIterator(), first = true; i.atEnd() == false; i++) { // Get client and check that it is valid @@ -4424,7 +4445,11 @@ std::wstring Server::getStatusString() if(player != NULL) name = narrow_to_wide(player->getName()); // Add name to information string - os<getMap()))->isSavingEnabled() == false) @@ -4522,6 +4547,73 @@ Inventory* Server::createDetachedInventory(const std::string &name) return inv; } +class BoolScopeSet +{ +public: + BoolScopeSet(bool *dst, bool val): + m_dst(dst) + { + m_orig_state = *m_dst; + *m_dst = val; + } + ~BoolScopeSet() + { + *m_dst = m_orig_state; + } +private: + bool *m_dst; + bool m_orig_state; +}; + +// actions: time-reversed list +// Return value: success/failure +bool Server::rollbackRevertActions(const std::list &actions, + std::list *log) +{ + infostream<<"Server::rollbackRevertActions(len="<getMap()); + // Disable rollback report sink while reverting + BoolScopeSet rollback_scope_disable(&m_rollback_sink_enabled, false); + + // Fail if no actions to handle + if(actions.empty()){ + log->push_back("Nothing to do."); + return false; + } + + int num_tried = 0; + int num_failed = 0; + + for(std::list::const_iterator + i = actions.begin(); + i != actions.end(); i++) + { + const RollbackAction &action = *i; + num_tried++; + bool success = action.applyRevert(map, this, this); + if(!success){ + num_failed++; + std::ostringstream os; + os<<"Revert of step ("<push_back(os.str()); + }else{ + std::ostringstream os; + os<<"Succesfully reverted step ("<push_back(os.str()); + } + } + + infostream<<"Map::rollbackRevertActions(): "< // Needed for rollbackRevertActions() struct LuaState; typedef struct lua_State lua_State; @@ -44,6 +46,7 @@ class IWritableNodeDefManager; class IWritableCraftDefManager; class EventManager; class PlayerSAO; +class IRollbackManager; class ServerError : public std::exception { @@ -543,6 +546,13 @@ public: // Envlock and conlock should be locked when using Lua lua_State *getLua(){ return m_lua; } + + // Envlock should be locked when using the rollback manager + IRollbackManager *getRollbackManager(){ return m_rollback; } + // actions: time-reversed list + // Return value: success/failure + bool rollbackRevertActions(const std::list &actions, + std::list *log); // IGameDef interface // Under envlock @@ -553,6 +563,7 @@ public: virtual u16 allocateUnknownNodeId(const std::string &name); virtual ISoundManager* getSoundManager(); virtual MtEventManager* getEventManager(); + virtual IRollbackReportSink* getRollbackReportSink(); IWritableItemDefManager* getWritableItemDefManager(); IWritableNodeDefManager* getWritableNodeDefManager(); @@ -720,6 +731,11 @@ private: // Bann checking BanManager m_banmanager; + // Rollback manager (behind m_env_mutex) + IRollbackManager *m_rollback; + bool m_rollback_sink_enabled; + bool m_enable_rollback_recording; // Updated once in a while + // Scripting // Envlock and conlock should be locked when using Lua lua_State *m_lua; diff --git a/src/servercommand.cpp b/src/servercommand.cpp deleted file mode 100644 index f14e0fba1f..0000000000 --- a/src/servercommand.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* -Part of Minetest-c55 -Copyright (C) 2010-11 celeron55, Perttu Ahola -Copyright (C) 2011 Ciaran Gultnieks - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#include "servercommand.h" -#include "settings.h" -#include "main.h" // For g_settings -#include "content_sao.h" - -#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" - -void cmd_status(std::wostringstream &os, - ServerCommandContext *ctx) -{ - os<server->getStatusString(); -} - -void cmd_me(std::wostringstream &os, - ServerCommandContext *ctx) -{ - if(!ctx->server->checkPriv(ctx->player->getName(), "shout")) - { - os<player->getName()); - os << L"* " << name << L" " << ctx->paramstring; - ctx->flags |= SEND_TO_OTHERS | SEND_NO_PREFIX; -} - -void cmd_time(std::wostringstream &os, - ServerCommandContext *ctx) -{ - if(ctx->parms.size() != 2) - { - os<server->checkPriv(ctx->player->getName(), "settime")) - { - os<parms[1])); - ctx->server->setTimeOfDay(time); - os<player->getName()<<" sets time " - <server->checkPriv(ctx->player->getName(), "server")) - { - os<player->getName() - <<" shuts down server"<server->requestShutdown(); - - os<flags |= SEND_TO_OTHERS; -} - -void cmd_banunban(std::wostringstream &os, ServerCommandContext *ctx) -{ - if(!ctx->server->checkPriv(ctx->player->getName(), "ban")) - { - os<parms.size() < 2) - { - std::string desc = ctx->server->getBanDescription(""); - os<parms[0] == L"ban") - { - Player *player = ctx->env->getPlayer(wide_to_narrow(ctx->parms[1]).c_str()); - - if(player == NULL) - { - os<server->getPeerAddress(player->peer_id); - std::string ip_string = address.serializeString(); - ctx->server->setIpBanned(ip_string, player->getName()); - os<getName()); - - actionstream<player->getName()<<" bans " - <getName()<<" / "<parms[1]); - std::string desc = ctx->server->getBanDescription(ip_or_name); - ctx->server->unsetIpBanned(ip_or_name); - os<player->getName()<<" unbans " - <server->checkPriv(ctx->player->getName(), "server")) - { - os<player->getName() - <<" clears all objects"<player->getName()); - msg += L")"; - ctx->server->notifyPlayers(msg); - } - - ctx->env->clearAllObjects(); - - actionstream<<"object clearing done"<flags |= SEND_TO_OTHERS; -} - - -std::wstring processServerCommand(ServerCommandContext *ctx) -{ - std::wostringstream os(std::ios_base::binary); - ctx->flags = SEND_TO_SENDER; // Default, unless we change it. - - if(ctx->parms.size() == 0) - os<parms[0] == L"status") - cmd_status(os, ctx); - else if(ctx->parms[0] == L"time") - cmd_time(os, ctx); - else if(ctx->parms[0] == L"shutdown") - cmd_shutdown(os, ctx); - else if(ctx->parms[0] == L"ban" || ctx->parms[0] == L"unban") - cmd_banunban(os, ctx); - else if(ctx->parms[0] == L"me") - cmd_me(os, ctx); - else if(ctx->parms[0] == L"clearobjects") - cmd_clearobjects(os, ctx); - else - os<parms[0]; - - return os.str(); -} - - diff --git a/src/servercommand.h b/src/servercommand.h deleted file mode 100644 index c0f78a9f2e..0000000000 --- a/src/servercommand.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -Part of Minetest-c55 -Copyright (C) 2010-11 celeron55, Perttu Ahola -Copyright (C) 2011 Ciaran Gultnieks - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef SERVERCOMMAND_HEADER -#define SERVERCOMMAND_HEADER - -#include -#include -#include "irrlichttypes.h" -#include "player.h" -#include "server.h" - -#define SEND_TO_SENDER (1<<0) -#define SEND_TO_OTHERS (1<<1) -#define SEND_NO_PREFIX (1<<2) - -struct ServerCommandContext -{ - std::vector parms; - std::wstring paramstring; - Server* server; - ServerEnvironment *env; - Player* player; - u32 flags; - - ServerCommandContext( - std::vector parms, - std::wstring paramstring, - Server* server, - ServerEnvironment *env, - Player* player) - : parms(parms), paramstring(paramstring), - server(server), env(env), player(player) - { - } - -}; - -// Process a command sent from a client. The environment and connection -// should be locked when this is called. -// Returns a response message, to be dealt with according to the flags set -// in the context. -std::wstring processServerCommand(ServerCommandContext *ctx); - -#endif - - diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index d2f7276a6c..c78f6288fd 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -30,7 +30,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #elif defined(__APPLE__) #include #include - #include + //#include #else #include #include diff --git a/src/test.cpp b/src/test.cpp index fe5a4f232d..f81f2910c6 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -698,6 +698,7 @@ struct TestInventory: public TestBase { std::string serialized_inventory = "List 0 32\n" + "Width 3\n" "Empty\n" "Empty\n" "Empty\n" @@ -735,6 +736,7 @@ struct TestInventory: public TestBase std::string serialized_inventory_2 = "List main 32\n" + "Width 5\n" "Empty\n" "Empty\n" "Empty\n" @@ -778,6 +780,8 @@ struct TestInventory: public TestBase inv.getList("0")->setName("main"); UASSERT(!inv.getList("0")); UASSERT(inv.getList("main")); + UASSERT(inv.getList("main")->getWidth() == 3); + inv.getList("main")->setWidth(5); std::ostringstream inv_os(std::ios::binary); inv.serialize(inv_os); UASSERT(inv_os.str() == serialized_inventory_2); diff --git a/src/tile.cpp b/src/tile.cpp index 6dbe4c63a6..f7f1779ca5 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1200,10 +1200,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, // Position to copy the blitted from in the blitted image core::position2d pos_from(0,0); // Blit - image->copyToWithAlpha(baseimg, pos_to, + /*image->copyToWithAlpha(baseimg, pos_to, core::rect(pos_from, dim), video::SColor(255,255,255,255), - NULL); + NULL);*/ + blit_with_alpha(image, baseimg, pos_from, pos_to, dim); // Drop image image->drop(); } @@ -1344,7 +1345,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, u32 h0 = stoi(sf.next(":")); infostream<<"combined w="<createImage(video::ECF_A8R8G8B8, dim); img->copyTo(img2); img->drop(); - img2->copyToWithAlpha(baseimg, pos_base, + /*img2->copyToWithAlpha(baseimg, pos_base, core::rect(v2s32(0,0), dim), video::SColor(255,255,255,255), - NULL); + NULL);*/ + blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); img2->drop(); } else