From 0c91a0d59db70f3f502004d4c37fecd4e10c9401 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Thu, 26 Jul 2012 13:49:13 +0300 Subject: [PATCH 01/39] Working group-shapeless and multigroup recipes --- doc/lua_api.txt | 10 +++- src/craftdef.cpp | 120 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 100 insertions(+), 30 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 35b89021d3..d47281b2df 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 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 ("< 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() + 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 + minetest.chat_send_player(name, "Last actor on "..nodedesc.." was ".. + actor..", "..dump(act_seconds).."s ago") + end + end, +}) + +minetest.register_chatcommand("rollback", { + params = " [] | :liquid []", + 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) + for _,line in ipairs(log) do + minetest.chat_send_player(name, line) + end + if success then + minetest.chat_send_player(name, "Reverting actions succeeded.") + else + minetest.chat_send_player(name, "Reverting actions FAILED.") + end + end, +}) + 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 d47281b2df..46ea3a86ea 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -889,6 +889,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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e369b9623f..43d7f241a9 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 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/environment.cpp b/src/environment.cpp index 02ca1f71ba..9390101e75 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -349,6 +349,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"; diff --git a/src/environment.h b/src/environment.h index 0e4b85e06e..6c52b003d7 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; } 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/inventorymanager.cpp b/src/inventorymanager.cpp index 06978fbb9e..85668d6455 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(); + + // 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 */ @@ -575,6 +624,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) + { + 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) diff --git a/src/map.cpp b/src/map.cpp index dc1f45068c..734122105e 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); } @@ -1588,6 +1621,11 @@ void Map::transformLiquids(core::map & modified_blocks) DSTACK(__FUNCTION_NAME); //TimeTaker timer("transformLiquids()"); + /* + If something goes wrong, liquids are to blame + */ + RollbackScopeActor rollback_scope(m_gamedef->rollback(), "liquid"); + u32 loopcount = 0; u32 initial_size = m_transforming_liquid.size(); @@ -1791,7 +1829,22 @@ void Map::transformLiquids(core::map & modified_blocks) n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); } n0.setContent(new_node_content); + + // Get old node for rollback + RollbackNode rollback_oldnode(this, p0, m_gamedef); + + // Set node setNode(p0, n0); + + // Report for rollback + if(m_gamedef->rollback()) + { + RollbackNode rollback_newnode(this, p0, m_gamedef); + RollbackAction action; + action.setSetNode(p0, rollback_oldnode, rollback_newnode); + m_gamedef->rollback()->reportAction(action); + } + 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/rollback.cpp b/src/rollback.cpp new file mode 100644 index 0000000000..3fe7910503 --- /dev/null +++ b/src/rollback.cpp @@ -0,0 +1,293 @@ +/* +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 "inventorymanager.h" // deserializing InventoryLocations + +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + +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); + action.actor = m_current_actor; + infostream<<"RollbackManager::reportAction():" + <<" time="<::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<<" "; + std::string action_s = i->toString(); + 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->type == RollbackAction::TYPE_SET_NODE) + { + action_p = i->p; + } + else if(i->type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) + { + InventoryLocation loc; + loc.deSerialize(i->inventory_location); + if(loc.type != InventoryLocation::NODEMETA) + continue; + action_p = loc.p; + } + else + 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 void setActor(const std::string &actor) = 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..e15fe3da31 --- /dev/null +++ b/src/rollback_interface.cpp @@ -0,0 +1,398 @@ +/* +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::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; + + 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) + {} + + 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 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 void setActor(const std::string &actor) = 0; +}; + +class RollbackScopeActor +{ +public: + RollbackScopeActor(IRollbackReportSink *sink, const std::string &actor): + m_sink(sink) + { + if(m_sink){ + m_actor_was = m_sink->getActor(); + m_sink->setActor(actor); + } + } + ~RollbackScopeActor() + { + if(m_sink){ + m_sink->setActor(m_actor_was); + } + } +private: + IRollbackReportSink *m_sink; + std::string m_actor_was; +}; + +#endif diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index e1d9185420..61488abb4e 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) { @@ -2106,6 +2107,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; @@ -4853,6 +4855,55 @@ 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}, @@ -4880,6 +4931,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} }; diff --git a/src/server.cpp b/src/server.cpp index 21c936e699..a868a04256 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -54,6 +54,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/pointedthing.h" #include "util/mathconstants.h" +#include "rollback.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -934,6 +935,8 @@ 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_lua(NULL), m_itemdef(createItemDefManager()), m_nodedef(createNodeDefManager()), @@ -973,6 +976,10 @@ Server::Server( infostream<<"- config: "<getName()); + /* Note: Always set inventory not sent, to repair cases where the client made a bad prediction. @@ -2949,6 +2961,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 +3222,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) { @@ -4522,6 +4555,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,10 @@ private: // Bann checking BanManager m_banmanager; + // Rollback manager (behind m_env_mutex) + IRollbackManager *m_rollback; + bool m_rollback_sink_enabled; + // Scripting // Envlock and conlock should be locked when using Lua lua_State *m_lua; From f7dc72f8aacddea66312a38cb47cd5e928cebb5d Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 02:37:04 +0300 Subject: [PATCH 03/39] Properly rollback chat command triggered things --- src/server.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server.cpp b/src/server.cpp index a868a04256..6a126b60d1 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2627,6 +2627,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()); From 508b7b5e51bd2e193fb182bb9247555154d78087 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 02:46:54 +0300 Subject: [PATCH 04/39] Don't track liquids for rollback because of too much log --- builtin/chatcommands.lua | 2 +- src/map.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/builtin/chatcommands.lua b/builtin/chatcommands.lua index 49ae8c6337..28b5f4b1f3 100644 --- a/builtin/chatcommands.lua +++ b/builtin/chatcommands.lua @@ -538,7 +538,7 @@ minetest.register_chatcommand("rollback_check", { }) minetest.register_chatcommand("rollback", { - params = " [] | :liquid []", + params = " [] | : []", description = "revert actions of a player; default for is 60", privs = {rollback=true}, func = function(name, param) diff --git a/src/map.cpp b/src/map.cpp index 734122105e..853693049c 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1623,8 +1623,9 @@ void Map::transformLiquids(core::map & modified_blocks) /* If something goes wrong, liquids are to blame + NOTE: Do not track liquids; it causes huge amounts of rollback log */ - RollbackScopeActor rollback_scope(m_gamedef->rollback(), "liquid"); + //RollbackScopeActor rollback_scope(m_gamedef->rollback(), "liquid"); u32 loopcount = 0; u32 initial_size = m_transforming_liquid.size(); @@ -1831,19 +1832,19 @@ void Map::transformLiquids(core::map & modified_blocks) n0.setContent(new_node_content); // Get old node for rollback - RollbackNode rollback_oldnode(this, p0, m_gamedef); + //RollbackNode rollback_oldnode(this, p0, m_gamedef); // Set node setNode(p0, n0); // Report for rollback - if(m_gamedef->rollback()) + /*if(m_gamedef->rollback()) { RollbackNode rollback_newnode(this, p0, m_gamedef); RollbackAction action; action.setSetNode(p0, rollback_oldnode, rollback_newnode); m_gamedef->rollback()->reportAction(action); - } + }*/ v3s16 blockpos = getNodeBlockPos(p0); MapBlock *block = getBlockNoCreateNoEx(blockpos); From a9d8df83d228635594b75a563a0a8d906b3b883a Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 13:24:28 +0300 Subject: [PATCH 05/39] Make the rollback system VERY FUCKING GOD DAMN POWERFUL --- builtin/chatcommands.lua | 15 ++++-- src/map.cpp | 35 +++++++------ src/rollback.cpp | 102 ++++++++++++++++++++++++++++++------- src/rollback.h | 4 +- src/rollback_interface.cpp | 18 +++++++ src/rollback_interface.h | 20 ++++++-- 6 files changed, 150 insertions(+), 44 deletions(-) diff --git a/builtin/chatcommands.lua b/builtin/chatcommands.lua index 28b5f4b1f3..8e4639955b 100644 --- a/builtin/chatcommands.lua +++ b/builtin/chatcommands.lua @@ -520,6 +520,7 @@ minetest.register_chatcommand("rollback_check", { 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 @@ -531,8 +532,10 @@ minetest.register_chatcommand("rollback_check", { 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 - minetest.chat_send_player(name, "Last actor on "..nodedesc.." was ".. - actor..", "..dump(act_seconds).."s ago") + 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, }) @@ -557,8 +560,12 @@ minetest.register_chatcommand("rollback", { dump(target_name).." since "..dump(seconds).." seconds.") local success, log = minetest.rollback_revert_actions_by( target_name, seconds) - for _,line in ipairs(log) do - minetest.chat_send_player(name, line) + 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.") diff --git a/src/map.cpp b/src/map.cpp index 853693049c..8aa73efbe7 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1621,12 +1621,6 @@ void Map::transformLiquids(core::map & modified_blocks) DSTACK(__FUNCTION_NAME); //TimeTaker timer("transformLiquids()"); - /* - If something goes wrong, liquids are to blame - NOTE: Do not track liquids; it causes huge amounts of rollback log - */ - //RollbackScopeActor rollback_scope(m_gamedef->rollback(), "liquid"); - u32 loopcount = 0; u32 initial_size = m_transforming_liquid.size(); @@ -1830,21 +1824,30 @@ void Map::transformLiquids(core::map & modified_blocks) n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); } n0.setContent(new_node_content); - - // Get old node for rollback - //RollbackNode rollback_oldnode(this, p0, m_gamedef); - - // Set node - setNode(p0, n0); - // Report for rollback - /*if(m_gamedef->rollback()) - { + // Find out whether there is a suspect for this action + std::string suspect; + if(m_gamedef->rollback()){ + // Max. 5 seconds ago + suspect = m_gamedef->rollback()->getSuspect(p0, 5); + } + + 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); diff --git a/src/rollback.cpp b/src/rollback.cpp index 3fe7910503..db3f01702b 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -28,10 +28,34 @@ with this program; if not, write to the Free Software Foundation, Inc., #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<<")" +// 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 = +1 point) + f += 1.0 * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); + // Time (1 second = -1 point) + f -= 1.0 * (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: @@ -44,10 +68,23 @@ public: 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, 5); // 5s timeframe + 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; + // 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 > likely_suspect_nearness){ + likely_suspect_nearness = f; + likely_suspect = *i; + } + } + // No likely suspect was found + if(likely_suspect_nearness == 0) + return ""; + // Likely suspect was found + return likely_suspect.actor; } void flush() { @@ -80,8 +153,12 @@ public: of<<" "; of<actor); of<<" "; - std::string action_s = i->toString(); - of<toString(); + if(i->actor_is_guess){ + of<<" "; + of<<"actor_is_guess"; + } + of<type == RollbackAction::TYPE_SET_NODE) - { - action_p = i->p; - } - else if(i->type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) - { - InventoryLocation loc; - loc.deSerialize(i->inventory_location); - if(loc.type != InventoryLocation::NODEMETA) - continue; - action_p = loc.p; - } - else + if(!i->getPosition(&action_p)) continue; if(range == 0){ @@ -281,6 +346,7 @@ private: std::string m_filepath; IGameDef *m_gamedef; std::string m_current_actor; + bool m_current_actor_is_guess; std::list m_action_todisk_buffer; std::list m_action_latest_buffer; }; diff --git a/src/rollback.h b/src/rollback.h index b5428c4514..59db122e3e 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -33,7 +33,9 @@ public: // IRollbackReportManager virtual void reportAction(const RollbackAction &action) = 0; virtual std::string getActor() = 0; - virtual void setActor(const std::string &actor) = 0; + virtual bool isActorGuess() = 0; + virtual void setActor(const std::string &actor, bool is_guess) = 0; + virtual std::string getSuspect(v3s16 p, int max_time) = 0; virtual ~IRollbackManager(){} virtual void flush() = 0; diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp index e15fe3da31..b2eb2093c5 100644 --- a/src/rollback_interface.cpp +++ b/src/rollback_interface.cpp @@ -289,6 +289,24 @@ bool RollbackAction::isImportant(IGameDef *gamedef) const } } +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{ diff --git a/src/rollback_interface.h b/src/rollback_interface.h index 0f0a11885a..ac8368f4e4 100644 --- a/src/rollback_interface.h +++ b/src/rollback_interface.h @@ -65,6 +65,7 @@ struct RollbackAction int unix_time; std::string actor; + bool actor_is_guess; v3s16 p; RollbackNode n_old; @@ -77,7 +78,9 @@ struct RollbackAction std::string inventory_stack; RollbackAction(): - type(TYPE_NOTHING) + type(TYPE_NOTHING), + unix_time(0), + actor_is_guess(false) {} void setSetNode(v3s16 p_, const RollbackNode &n_old_, @@ -107,6 +110,8 @@ struct RollbackAction // 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; }; @@ -117,29 +122,34 @@ public: virtual ~IRollbackReportSink(){} virtual void reportAction(const RollbackAction &action) = 0; virtual std::string getActor() = 0; - virtual void setActor(const std::string &actor) = 0; + virtual bool isActorGuess() = 0; + virtual void setActor(const std::string &actor, bool is_guess) = 0; + virtual std::string getSuspect(v3s16 p, int max_time) = 0; }; class RollbackScopeActor { public: - RollbackScopeActor(IRollbackReportSink *sink, const std::string &actor): + RollbackScopeActor(IRollbackReportSink *sink, const std::string &actor, + bool is_guess=false): m_sink(sink) { if(m_sink){ m_actor_was = m_sink->getActor(); - m_sink->setActor(actor); + m_actor_was_guess = m_sink->isActorGuess(); + m_sink->setActor(actor, is_guess); } } ~RollbackScopeActor() { if(m_sink){ - m_sink->setActor(m_actor_was); + 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 From 98ff4eb4ee2ed22c5dc3ed2923bbd83d43a13436 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 13:43:25 +0300 Subject: [PATCH 06/39] Fix server build (a missing header) --- src/environment.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/environment.cpp b/src/environment.cpp index 9390101e75..571980498d 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<<")" From 1d44a98f2f6013c0f7ce82215c1faa11ad4a0579 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 13:45:49 +0300 Subject: [PATCH 07/39] ABM and liquid overload skip --- src/environment.cpp | 11 +++++++++-- src/environment.h | 1 + src/map.cpp | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/environment.cpp b/src/environment.cpp index 571980498d..e3e6210650 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -326,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) { @@ -1095,7 +1096,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"); @@ -1128,8 +1134,9 @@ void ServerEnvironment::step(float dtime) infostream<<"WARNING: active block modifiers took " < & 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 >= 1000) break; loopcount++; From 0de3fb786daf6d332b4022d18177d1ca0316532e Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 13:54:14 +0300 Subject: [PATCH 08/39] Increase automatic suspect guess timeframe --- src/rollback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rollback.cpp b/src/rollback.cpp index db3f01702b..14b8839ea2 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -76,7 +76,7 @@ public: v3s16 p; if(!action.getPosition(&p)) return; - action.actor = getSuspect(p, 5); // 5s timeframe + action.actor = getSuspect(p, 30); // 30s timeframe if(action.actor.empty()) return; action.actor_is_guess = true; From 7ef0a13250dfd0e153d485e8b3a8084327a81f52 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 14:52:29 +0300 Subject: [PATCH 09/39] Tweak rollback stuff --- src/map.cpp | 4 ++-- src/rollback.cpp | 13 +++++++++---- src/rollback.h | 3 ++- src/rollback_interface.h | 3 ++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index f8f0d017f9..8ddc4006e0 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1828,8 +1828,8 @@ void Map::transformLiquids(core::map & modified_blocks) // Find out whether there is a suspect for this action std::string suspect; if(m_gamedef->rollback()){ - // Max. 5 seconds ago - suspect = m_gamedef->rollback()->getSuspect(p0, 5); + // Max. 5 seconds ago, shortcut at 98 points + suspect = m_gamedef->rollback()->getSuspect(p0, 5, 95); } if(!suspect.empty()){ diff --git a/src/rollback.cpp b/src/rollback.cpp index 14b8839ea2..9cbcb6c257 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -43,8 +43,8 @@ static float getSuspectNearness(bool is_guess, v3s16 suspect_p, int suspect_t, return 0; // 0 = cannot be // Start from 100 int f = 100; - // Distance (1 node = +1 point) - f += 1.0 * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); + // Distance (1 node = -1 point) + f -= 1.0 * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); // Time (1 second = -1 point) f -= 1.0 * (action_t - suspect_t); // If is a guess, halve the points @@ -76,7 +76,8 @@ public: v3s16 p; if(!action.getPosition(&p)) return; - action.actor = getSuspect(p, 30); // 30s timeframe + // 60s default timeframe, 95 points shortcut + action.actor = getSuspect(p, 60, 95); if(action.actor.empty()) return; action.actor_is_guess = true; @@ -102,7 +103,7 @@ public: m_current_actor = actor; m_current_actor_is_guess = is_guess; } - std::string getSuspect(v3s16 p, int max_time) + std::string getSuspect(v3s16 p, int max_time, float nearness_shortcut) { if(m_current_actor != "") return m_current_actor; @@ -116,6 +117,8 @@ public: { 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)) @@ -125,6 +128,8 @@ public: if(f > likely_suspect_nearness){ likely_suspect_nearness = f; likely_suspect = *i; + if(likely_suspect_nearness >= nearness_shortcut) + break; } } // No likely suspect was found diff --git a/src/rollback.h b/src/rollback.h index 59db122e3e..0ae80c51d3 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -35,7 +35,8 @@ public: 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, int max_time) = 0; + // nearness_shortcut: 100 = same second, same node; 90 = 10s or 10 nodes + virtual std::string getSuspect(v3s16 p, int max_time, float nearness_shortcut) = 0; virtual ~IRollbackManager(){} virtual void flush() = 0; diff --git a/src/rollback_interface.h b/src/rollback_interface.h index ac8368f4e4..119379ae7e 100644 --- a/src/rollback_interface.h +++ b/src/rollback_interface.h @@ -124,7 +124,8 @@ public: 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, int max_time) = 0; + // nearness_shortcut: 100 = same second, same node; 90 = 10s or 10 nodes + virtual std::string getSuspect(v3s16 p, int max_time, float nearness_shortcut=98) = 0; }; class RollbackScopeActor From 3e754382af5d57b20fc2f6d59f991c9a79ccb0f7 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 15:46:51 +0300 Subject: [PATCH 10/39] Tweak rollback and liquids --- src/map.cpp | 5 ++--- src/rollback.cpp | 19 ++++++++++--------- src/rollback.h | 4 ++-- src/rollback_interface.h | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index 8ddc4006e0..824553b372 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1636,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 || loopcount >= 1000) + if(loopcount >= initial_size || loopcount >= 10000) break; loopcount++; @@ -1828,8 +1828,7 @@ void Map::transformLiquids(core::map & modified_blocks) // Find out whether there is a suspect for this action std::string suspect; if(m_gamedef->rollback()){ - // Max. 5 seconds ago, shortcut at 98 points - suspect = m_gamedef->rollback()->getSuspect(p0, 5, 95); + suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1); } if(!suspect.empty()){ diff --git a/src/rollback.cpp b/src/rollback.cpp index 9cbcb6c257..f0e3c40aa5 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -33,6 +33,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #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, @@ -43,10 +45,10 @@ static float getSuspectNearness(bool is_guess, v3s16 suspect_p, int suspect_t, return 0; // 0 = cannot be // Start from 100 int f = 100; - // Distance (1 node = -1 point) - f -= 1.0 * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); - // Time (1 second = -1 point) - f -= 1.0 * (action_t - suspect_t); + // 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; @@ -76,8 +78,7 @@ public: v3s16 p; if(!action.getPosition(&p)) return; - // 60s default timeframe, 95 points shortcut - action.actor = getSuspect(p, 60, 95); + action.actor = getSuspect(p, 83, 1); if(action.actor.empty()) return; action.actor_is_guess = true; @@ -103,12 +104,12 @@ public: m_current_actor = actor; m_current_actor_is_guess = is_guess; } - std::string getSuspect(v3s16 p, int max_time, float nearness_shortcut) + std::string getSuspect(v3s16 p, float nearness_shortcut, float min_nearness) { if(m_current_actor != "") return m_current_actor; int cur_time = time(0); - int first_time = cur_time - max_time; + int first_time = cur_time - (100-min_nearness); RollbackAction likely_suspect; float likely_suspect_nearness = 0; for(std::list::const_reverse_iterator @@ -125,7 +126,7 @@ public: continue; float f = getSuspectNearness(i->actor_is_guess, suspect_p, i->unix_time, p, cur_time); - if(f > likely_suspect_nearness){ + if(f >= min_nearness && f > likely_suspect_nearness){ likely_suspect_nearness = f; likely_suspect = *i; if(likely_suspect_nearness >= nearness_shortcut) diff --git a/src/rollback.h b/src/rollback.h index 0ae80c51d3..5e76042b5c 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -35,8 +35,8 @@ public: virtual std::string getActor() = 0; virtual bool isActorGuess() = 0; virtual void setActor(const std::string &actor, bool is_guess) = 0; - // nearness_shortcut: 100 = same second, same node; 90 = 10s or 10 nodes - virtual std::string getSuspect(v3s16 p, int max_time, float nearness_shortcut) = 0; + virtual std::string getSuspect(v3s16 p, float nearness_shortcut, + float min_nearness) = 0; virtual ~IRollbackManager(){} virtual void flush() = 0; diff --git a/src/rollback_interface.h b/src/rollback_interface.h index 119379ae7e..8dd429d766 100644 --- a/src/rollback_interface.h +++ b/src/rollback_interface.h @@ -124,8 +124,8 @@ public: virtual std::string getActor() = 0; virtual bool isActorGuess() = 0; virtual void setActor(const std::string &actor, bool is_guess) = 0; - // nearness_shortcut: 100 = same second, same node; 90 = 10s or 10 nodes - virtual std::string getSuspect(v3s16 p, int max_time, float nearness_shortcut=98) = 0; + virtual std::string getSuspect(v3s16 p, float nearness_shortcut, + float min_nearness) = 0; }; class RollbackScopeActor From e64feefc61e0fd104bfc40c61411b67423734924 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 27 Jul 2012 19:03:15 +0300 Subject: [PATCH 11/39] Handle 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; From c9ed379e39be94b588f4b62f893760c53b2d2a4f Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 28 Jul 2012 03:08:09 +0300 Subject: [PATCH 12/39] Add enable_rollback_recording setting, defaulting to false --- minetest.conf.example | 2 ++ src/defaultsettings.cpp | 1 + src/server.cpp | 7 +++++++ src/server.h | 1 + 4 files changed, 11 insertions(+) diff --git a/minetest.conf.example b/minetest.conf.example index fdfbf201cd..adeb3c8f68 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -160,6 +160,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/defaultsettings.cpp b/src/defaultsettings.cpp index dabdbb4aa5..6b85feb600 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -122,6 +122,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/server.cpp b/src/server.cpp index 6a126b60d1..05dff06c5c 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -937,6 +937,7 @@ Server::Server( 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()), @@ -1873,6 +1874,10 @@ void Server::AsyncRunStep() counter = 0.0; m_emergethread.trigger(); + + // Update m_enable_rollback_recording here too + m_enable_rollback_recording = + g_settings->getBool("enable_rollback_recording"); } } @@ -4658,6 +4663,8 @@ MtEventManager* Server::getEventManager() } IRollbackReportSink* Server::getRollbackReportSink() { + if(!m_enable_rollback_recording) + return NULL; if(!m_rollback_sink_enabled) return NULL; return m_rollback; diff --git a/src/server.h b/src/server.h index 668d424167..223c1b0ffd 100644 --- a/src/server.h +++ b/src/server.h @@ -734,6 +734,7 @@ private: // 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 From fd7ec2da911ff6eeb62035ad4457f1e5179dbaa1 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 28 Jul 2012 14:44:18 +0300 Subject: [PATCH 13/39] Fix inventory segfault when rollback recording is disabled --- src/inventorymanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 85668d6455..7fc8c8ce45 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -355,7 +355,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame /* Record rollback information */ - if(!ignore_rollback) + if(!ignore_rollback && gamedef->rollback()) { IRollbackReportSink *rollback = gamedef->rollback(); @@ -628,7 +628,7 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame /* Record rollback information */ - if(!ignore_src_rollback) + if(!ignore_src_rollback && gamedef->rollback()) { IRollbackReportSink *rollback = gamedef->rollback(); From d38b465b7c11e5f22632b60b88d65fd1e72282af Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 28 Jul 2012 16:31:45 +0300 Subject: [PATCH 14/39] Call this 0.4.2-rc1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() From a9a475ad50ae93c253e0d1eda1f71a42dbc20298 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sun, 12 Aug 2012 11:09:38 +0300 Subject: [PATCH 15/39] Remove unwanted ! from ifs in inventory record-for-rollback code --- src/inventorymanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 7fc8c8ce45..424b02b53f 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -360,7 +360,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame IRollbackReportSink *rollback = gamedef->rollback(); // If source is not infinite, record item take - if(!src_can_take_count != -1){ + if(src_can_take_count != -1){ RollbackAction action; std::string loc; { @@ -373,7 +373,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame rollback->reportAction(action); } // If destination is not infinite, record item put - if(!dst_can_put_count != -1){ + if(dst_can_put_count != -1){ RollbackAction action; std::string loc; { @@ -633,7 +633,7 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame IRollbackReportSink *rollback = gamedef->rollback(); // If source is not infinite, record item take - if(!src_can_take_count != -1){ + if(src_can_take_count != -1){ RollbackAction action; std::string loc; { From d05e3adbc7fe912319a14d5c164941017f2c7170 Mon Sep 17 00:00:00 2001 From: darkrose Date: Fri, 10 Aug 2012 12:34:02 +1000 Subject: [PATCH 16/39] fix node timers so on_timer gets the correct position --- src/environment.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/environment.cpp b/src/environment.cpp index e3e6210650..b88f55dead 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -1087,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)); } } From e3b831e97505a6bd6a07813b2a0b8608637a3cd9 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sun, 12 Aug 2012 15:49:06 +0300 Subject: [PATCH 17/39] Fix wrong amount of nodes being dropped from inventory --- src/inventorymanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 424b02b53f..ec4d47def3 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -575,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))) { From 1ed559bd2418dc34689160df09af8f779300ee9f Mon Sep 17 00:00:00 2001 From: Matthew I Date: Mon, 30 Jul 2012 09:45:26 -0400 Subject: [PATCH 18/39] Allow digging of unknown nodes This allows the removal of nodes with unknown types. get_item_callback() (C++) would fail if a node has an unknown type. Now it will try using the callback from minetest.nodedef_default in this case. Also, minetest.node_dig() (Lua) was altered to always allow digging when the node definition is empty (i.e. unknown node). --- builtin/item.lua | 3 ++- src/scriptapi.cpp | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/builtin/item.lua b/builtin/item.lua index 1b4b4d25a1..4ed20e4f1a 100644 --- a/builtin/item.lua +++ b/builtin/item.lua @@ -262,7 +262,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/src/scriptapi.cpp b/src/scriptapi.cpp index b8b3cb73ae..a1975971f9 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -5510,6 +5510,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) { @@ -5522,9 +5524,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 \""< Date: Sat, 28 Jul 2012 15:49:23 -0400 Subject: [PATCH 19/39] Remove trailing comma after player list in server status string --- src/server.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 05dff06c5c..85e361cede 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -4449,9 +4449,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 @@ -4466,7 +4467,11 @@ std::wstring Server::getStatusString() if(player != NULL) name = narrow_to_wide(player->getName()); // Add name to information string - os<getMap()))->isSavingEnabled() == false) From 548da26ddc1e38bffbc05c5577fe9a60476f8e71 Mon Sep 17 00:00:00 2001 From: Matthew I Date: Sat, 28 Jul 2012 15:27:31 -0400 Subject: [PATCH 20/39] Extend Lua API in order to implement chat commands minetest.get_server_status() minetest.request_shutdown() EnvRef:clear_objects() --- doc/lua_api.txt | 6 ++++++ src/scriptapi.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 46ea3a86ea..dea472f3f6 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -928,6 +928,10 @@ 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 + Random: minetest.get_connected_players() -> list of ObjectRefs minetest.hash_node_position({x=,y=,z=}) -> 48-bit integer @@ -1017,6 +1021,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) diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index a1975971f9..a6eaabf974 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -3810,6 +3810,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) @@ -3891,6 +3900,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} }; @@ -4170,6 +4180,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) { @@ -4911,6 +4935,8 @@ static int l_rollback_revert_actions_by(lua_State *L) 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}, From 8a3c777c40e2b61c1ba561ba0c60ab8d281886d2 Mon Sep 17 00:00:00 2001 From: Matthew I Date: Wed, 1 Aug 2012 17:17:52 -0400 Subject: [PATCH 21/39] Add ban management routines to Lua API Those functions are: * minetest.get_ban_list() * minetest.get_ban_description(ip_or_name) * minetest.ban_player(name) * minetest.unban_player_or_ip(ip_or_name) --- doc/lua_api.txt | 6 ++++++ src/scriptapi.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index dea472f3f6..8a6ea5dea7 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -932,6 +932,12 @@ 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 diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index a6eaabf974..95e2b5d01a 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -4584,6 +4584,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) { @@ -4946,6 +4996,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}, From b29d609b0bf54345f9c7cb8a1e3d6de6799fa147 Mon Sep 17 00:00:00 2001 From: Matthew I Date: Sun, 22 Jul 2012 09:42:43 -0400 Subject: [PATCH 22/39] Move chat commands to Lua and remove servercommand.{cpp,h} Commands moved: /me /status /time /shutdown /ban /clearobjects --- builtin/chatcommands.lua | 122 +++++++++++++++++++++---- src/CMakeLists.txt | 1 - src/server.cpp | 35 ++----- src/servercommand.cpp | 191 --------------------------------------- src/servercommand.h | 62 ------------- 5 files changed, 109 insertions(+), 302 deletions(-) delete mode 100644 src/servercommand.cpp delete mode 100644 src/servercommand.h diff --git a/builtin/chatcommands.lua b/builtin/chatcommands.lua index 8e4639955b..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/", @@ -575,3 +568,92 @@ minetest.register_chatcommand("rollback", { 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/src/CMakeLists.txt b/src/CMakeLists.txt index 43d7f241a9..8cdaa510d8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -193,7 +193,6 @@ set(common_SRCS connection.cpp environment.cpp server.cpp - servercommand.cpp socket.cpp mapblock.cpp mapsector.cpp diff --git a/src/server.cpp b/src/server.cpp index 85e361cede..ba99f47071 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -27,7 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "constants.h" #include "voxel.h" #include "config.h" -#include "servercommand.h" #include "filesys.h" #include "mapblock.h" #include "serverobject.h" @@ -2653,36 +2652,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 { 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 - - From b717b6505b4d401b901d5e07412aa580ff6ec462 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sun, 12 Aug 2012 17:11:23 +0300 Subject: [PATCH 23/39] Fix crash when furnace is full (minimal game) --- games/minimal/mods/default/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 0b1ab41a954e2a2b035888e200d775372a3b394e Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sun, 19 Aug 2012 10:55:04 +0300 Subject: [PATCH 24/39] Fix github issue #213: Dropping unknown items crashes the game The items will now just disappear when dropped. --- builtin/item.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/builtin/item.lua b/builtin/item.lua index 4ed20e4f1a..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 From 1d7408a7b866a8948e7913a7e8bac7d608f90fbb Mon Sep 17 00:00:00 2001 From: Matthew I Date: Sat, 28 Jul 2012 21:41:32 -0400 Subject: [PATCH 25/39] Fix black display inside opaque water Opaque water's solidness was being set to 2, like a normal node. When you swim, it is treated like a solid block, and the display goes black. Setting it to 1 like transparent water allows you to see. It looks somewhat awkward when you swim, look up, and see an opaque wall of water (the surface), but there isn't much that can be done about it. If you made the water transparent so it looked good, it would defeat the purpose :) . --- src/nodedef.cpp | 2 -- 1 file changed, 2 deletions(-) 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; From 8887105ffda38d42eb7dd3958f574317d7ce2ca6 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 31 Aug 2012 17:58:55 +0300 Subject: [PATCH 26/39] Properly initialize baseimg for texture modifier [combine:WxH:X,Y=filename:X,Y=filename2 --- src/tile.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tile.cpp b/src/tile.cpp index 6dbe4c63a6..a9ded38917 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1344,7 +1344,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, u32 h0 = stoi(sf.next(":")); infostream<<"combined w="< Date: Fri, 31 Aug 2012 18:06:47 +0300 Subject: [PATCH 27/39] Use proper alpha blit in some more texture modifiers --- src/tile.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tile.cpp b/src/tile.cpp index a9ded38917..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(); } @@ -1368,10 +1369,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, driver->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 From f60227f112a36d943c83d4d1ac0b24754fbd537e Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 1 Sep 2012 09:23:39 +0300 Subject: [PATCH 28/39] Don't crash in "unexpected multibyte character"; just print it in log. Github #222 --- src/keycode.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/keycode.cpp b/src/keycode.cpp index df3ebc9e31..5a8df0db47 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 { @@ -295,7 +297,10 @@ KeyPress::KeyPress(const irr::SEvent::SKeyInput &in) } else { m_name.resize(MB_CUR_MAX+1, '\0'); int written = wctomb(&m_name[0], Char); - assert (written >= 0 && "unexpected multibyte character"); + if(written >= 0){ + std::string hexstr = hex_encode((const char*)&Char, sizeof(Char)); + errorstream<<"KeyPress: Unexpected multibyte character "< Date: Sun, 19 Aug 2012 13:15:42 +0200 Subject: [PATCH 29/39] Make shift the default descent control on ladders and when flying "aux1_descends" setting switches to the old descend control. --- minetest.conf.example | 8 ++++- src/defaultsettings.cpp | 1 + src/localplayer.cpp | 75 +++++++++++++++++++++++++++++++---------- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/minetest.conf.example b/minetest.conf.example index adeb3c8f68..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 diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6b85feb600..3b37c18dce 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -52,6 +52,7 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_toggle_profiler", "KEY_F6"); settings->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"); diff --git a/src/localplayer.cpp b/src/localplayer.cpp index c1bfa34221..ee4fcd4e50 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -359,31 +359,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); + } } } From 6a16075912d016926ee0361fb85f9979c119be52 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Mon, 20 Aug 2012 01:29:56 +0400 Subject: [PATCH 30/39] Add InventoryList width property & allow custom crafting grids. --- doc/lua_api.txt | 2 ++ src/inventory.cpp | 21 +++++++++++++++++++++ src/inventory.h | 4 +++- src/inventorymanager.cpp | 10 ++++------ src/player.cpp | 3 ++- src/scriptapi.cpp | 33 +++++++++++++++++++++++++++++++++ src/test.cpp | 4 ++++ 7 files changed, 69 insertions(+), 8 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 8a6ea5dea7..88c594eb2c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1118,6 +1118,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/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 ec4d47def3..e2e5378383 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -769,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 @@ -793,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/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/scriptapi.cpp b/src/scriptapi.cpp index 95e2b5d01a..81e96aec64 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -1847,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) { @@ -1869,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) { @@ -2062,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), 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); From b17d3e7ad7e59a10df50277c15174529f20d0bed Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 1 Sep 2012 12:07:27 +0300 Subject: [PATCH 31/39] Add disable_jump and fall_damage_add_percent node groups --- doc/lua_api.txt | 2 ++ src/environment.cpp | 5 +++++ src/localplayer.cpp | 14 ++++++++++++-- src/localplayer.h | 1 + 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 88c594eb2c..008c9d40ca 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -453,6 +453,8 @@ 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) Known damage and digging time defining groups ---------------------------------------------- diff --git a/src/environment.cpp b/src/environment.cpp index b88f55dead..2926795425 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -2040,6 +2040,11 @@ void ClientEnvironment::step(float dtime) //f32 tolerance = BS*12; // 3 without damage f32 tolerance = BS*14; // 5 without damage f32 factor = 1; + const ContentFeatures &f = m_gamedef->ndef()-> + get(m_map->getNodeNoEx(lplayer->getStandingNodePos())); + // Determine fall damage multiplier + int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); + info.speed *= (1.0 + (float)addp/100.0); if(info.speed > tolerance) { f32 damage_f = (info.speed - tolerance)/BS*factor; diff --git a/src/localplayer.cpp b/src/localplayer.cpp index ee4fcd4e50..16111629e6 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -38,7 +38,8 @@ LocalPlayer::LocalPlayer(IGameDef *gamedef): m_sneak_node_exists(false), m_old_node_below(32767,32767,32767), m_old_node_below_type("air"), - m_need_to_get_new_sneak_node(true) + m_need_to_get_new_sneak_node(true), + m_can_jump(false) { // Initialize hp to 0, so that no hearts will be shown if server // doesn't support health points @@ -314,6 +315,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) @@ -459,7 +469,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 From 3b43c69df4ce7d68aebb4bd02aaa4aca18b9afad Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 1 Sep 2012 12:58:37 +0300 Subject: [PATCH 32/39] Add bouncy node group --- doc/lua_api.txt | 1 + src/collision.cpp | 55 ++++++++++++++++++++++++++++++++++++++++----- src/collision.h | 34 +++++++++++++++++++--------- src/environment.cpp | 29 ++++++++++++------------ src/localplayer.cpp | 24 +++++++++++++------- 5 files changed, 104 insertions(+), 39 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 008c9d40ca..d3e254a2d8 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -455,6 +455,7 @@ Special groups - 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 ---------------------------------------------- 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/environment.cpp b/src/environment.cpp index 2926795425..24943ad5fa 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -2033,25 +2033,26 @@ void ClientEnvironment::step(float dtime) i = player_collisions.begin(); i != player_collisions.end(); i++) { + 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 CollisionInfo &info = *i; - if(info.t == COLLISION_FALL) + 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; const ContentFeatures &f = m_gamedef->ndef()-> - get(m_map->getNodeNoEx(lplayer->getStandingNodePos())); + get(m_map->getNodeNoEx(info.node_p)); // Determine fall damage multiplier int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); - info.speed *= (1.0 + (float)addp/100.0); - 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); - } + pre_factor = 1.0 + (float)addp/100.0; + } + float speed = (info.new_speed - info.old_speed).getLength(); + speed *= pre_factor; + 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/localplayer.cpp b/src/localplayer.cpp index 16111629e6..15b6fd15de 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -189,7 +189,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, bool touching_ground_was = touching_ground; touching_ground = result.touching_ground; - bool standing_on_unloaded = result.standing_on_unloaded; + //bool standing_on_unloaded = result.standing_on_unloaded; /* Check the nodes under the player to see from which node the @@ -282,18 +282,25 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, /* Report collisions */ + bool bouncy_jump = false; if(collision_info) { - // Report fall collision - if(old_speed.Y < m_speed.Y - 0.1 && !standing_on_unloaded) - { - CollisionInfo info; - info.t = COLLISION_FALL; - info.speed = m_speed.Y - old_speed.Y; + for(size_t i=0; ipush_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); @@ -479,8 +486,9 @@ void LocalPlayer::applyControl(float dtime) v3f speed = getSpeed(); if(speed.Y >= -0.5*BS) { - speed.Y = 6.5*BS; + speed.Y += 6.5*BS; setSpeed(speed); + m_can_jump = false; MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_gamedef->event()->put(e); From c129b3852b97b7bd7ea7fe904af68243ad44a333 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 1 Sep 2012 13:21:52 +0300 Subject: [PATCH 33/39] Fix the bouncy node related stuff a bit --- src/environment.cpp | 13 ++++++++++--- src/localplayer.cpp | 3 +-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/environment.cpp b/src/environment.cpp index 24943ad5fa..a49a5e384e 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -2033,10 +2033,18 @@ void ClientEnvironment::step(float dtime) i = player_collisions.begin(); i != player_collisions.end(); i++) { + CollisionInfo &info = *i; + v3f speed_diff = info.new_speed - info.old_speed;; + // Handle only fall damage + // (because otherwise walking against something in fast_move kills you) + if(speed_diff.Y < 0 || info.old_speed.Y >= 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 - CollisionInfo &info = *i; if(info.type == COLLISION_NODE) { const ContentFeatures &f = m_gamedef->ndef()-> @@ -2045,8 +2053,7 @@ void ClientEnvironment::step(float dtime) int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); pre_factor = 1.0 + (float)addp/100.0; } - float speed = (info.new_speed - info.old_speed).getLength(); - speed *= pre_factor; + float speed = pre_factor * speed_diff.getLength(); if(speed > tolerance) { f32 damage_f = (speed - tolerance)/BS * post_factor; diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 15b6fd15de..46a10c90b3 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -486,9 +486,8 @@ void LocalPlayer::applyControl(float dtime) v3f speed = getSpeed(); if(speed.Y >= -0.5*BS) { - speed.Y += 6.5*BS; + speed.Y = 6.5*BS; setSpeed(speed); - m_can_jump = false; MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_gamedef->event()->put(e); From 0f7728f84decb836bd7d025456315c297fe30435 Mon Sep 17 00:00:00 2001 From: Sergey Gilfanov Date: Thu, 26 Jul 2012 22:52:05 +0400 Subject: [PATCH 34/39] Smooth transition to the desert biome. --- src/mapgen.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mapgen.cpp b/src/mapgen.cpp index af53d0091d..b37d03501a 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; }; From c0bd96d9b314e78a7aeb74b1bff70e1023b2f9e2 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 1 Sep 2012 13:41:15 +0300 Subject: [PATCH 35/39] Attempt to fix flying gravel and dirt --- src/mapgen.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mapgen.cpp b/src/mapgen.cpp index b37d03501a..67e92f4498 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -1761,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; From 3e7957512bdb86acb3836c6c1df96e38e05efa3c Mon Sep 17 00:00:00 2001 From: Thomas Lauro Date: Thu, 26 Jul 2012 09:49:00 +0200 Subject: [PATCH 36/39] A bunch of GUIKeyChangeMenu fixes - Display message when associating a key already in use - Fix issue for not beeing able to assign space to an action - Cleaning up - Make it build (by celeron55) --- src/guiKeyChangeMenu.cpp | 657 +++++++++------------------------------ src/guiKeyChangeMenu.h | 47 +-- 2 files changed, 154 insertions(+), 550 deletions(-) diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index db29fb376d..a7dbc8c6f7 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,14 @@ 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) { 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 +77,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 +90,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 +107,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 +171,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 +201,58 @@ 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); - 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"); + 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 +260,73 @@ 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(); + 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..b81866983b 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,13 @@ 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); 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 From 5194505407884f6375311f0ab4c5f8783646cb91 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 1 Sep 2012 18:02:29 +0300 Subject: [PATCH 37/39] Fix GUIKeyChangeMenu so that '/' can be inserted on a finnish keyboard --- src/guiKeyChangeMenu.cpp | 22 +++++++++++++++++++--- src/guiKeyChangeMenu.h | 2 ++ src/keycode.cpp | 15 +++++++++++++-- src/keycode.h | 2 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index a7dbc8c6f7..49b292df40 100644 --- a/src/guiKeyChangeMenu.cpp +++ b/src/guiKeyChangeMenu.cpp @@ -60,6 +60,7 @@ GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) : GUIModalMenu(env, parent, id, menumgr) { + shift_down = false; activeKey = -1; this->key_used_text = NULL; init_keys(); @@ -204,7 +205,15 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) && 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; // Remove Key already in use message if(this->key_used_text) @@ -240,8 +249,14 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) this->key_used.push_back(kp); changeCtype("C"); - activeKey = -1; - return true; + // Allow characters made with shift + if(shift_went_down){ + shift_down = true; + return false; + }else{ + activeKey = -1; + return true; + } } } if (event.EventType == EET_GUI_EVENT) @@ -287,6 +302,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) 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(), diff --git a/src/guiKeyChangeMenu.h b/src/guiKeyChangeMenu.h index b81866983b..beb4f0b6fc 100644 --- a/src/guiKeyChangeMenu.h +++ b/src/guiKeyChangeMenu.h @@ -66,6 +66,8 @@ private: void add_key(int id, std::string setting_name, std::string button_name); + bool shift_down; + s32 activeKey; std::vector key_used; diff --git a/src/keycode.cpp b/src/keycode.cpp index 5a8df0db47..cdf3c6062d 100644 --- a/src/keycode.cpp +++ b/src/keycode.cpp @@ -288,16 +288,27 @@ 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){ + if(written < 0){ std::string hexstr = hex_encode((const char*)&Char, sizeof(Char)); errorstream<<"KeyPress: Unexpected multibyte character "< Date: Wed, 25 Jul 2012 15:56:09 +0200 Subject: [PATCH 38/39] Fix issue with openal mac os x compile --- src/sound_openal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b6c12d2aa3f7f3f0faca86a0b20904e309c15179 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 1 Sep 2012 18:32:15 +0300 Subject: [PATCH 39/39] Fix github issue #224 --- src/server.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index ba99f47071..2da9cbe244 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -442,9 +442,12 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, m_nearest_unsent_reset_timer += dtime; if(m_nothing_to_send_pause_timer >= 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);