/* Minetest Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> 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 <sstream> #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<<"("<<itos(p.X)<<","<<itos(p.Y)<<","<<itos(p.Z)<<")"; os<<" "; os<<serializeJsonString(n_old.name); os<<" "; os<<itos(n_old.param1); os<<" "; os<<itos(n_old.param2); os<<" "; os<<serializeJsonString(n_old.meta); os<<" "; os<<serializeJsonString(n_new.name); os<<" "; os<<itos(n_new.param1); os<<" "; os<<itos(n_new.param2); os<<" "; os<<serializeJsonString(n_new.meta); os<<"]"; return os.str(); } case TYPE_MODIFY_INVENTORY_STACK: { std::ostringstream os(std::ios::binary); os<<"[modify_inventory_stack"; os<<" "; os<<serializeJsonString(inventory_location); os<<" "; os<<serializeJsonString(inventory_list); os<<" "; os<<inventory_index; os<<" "; os<<(inventory_add?"add":"remove"); os<<" "; os<<serializeJsonString(inventory_stack); os<<"]"; return os.str(); } default: return "none"; } } void RollbackAction::fromStream(std::istream &is) throw(SerializationError) { int c = is.get(); if(c != '['){ is.putback(c); throw SerializationError("RollbackAction: starting [ not found"); } std::string id; std::getline(is, id, ' '); if(id == "set_node") { c = is.get(); if(c != '('){ is.putback(c); throw SerializationError("RollbackAction: starting ( not found"); } // Position std::string px_raw; std::string py_raw; std::string pz_raw; std::getline(is, px_raw, ','); std::getline(is, py_raw, ','); std::getline(is, pz_raw, ')'); c = is.get(); if(c != ' '){ is.putback(c); throw SerializationError("RollbackAction: after-p ' ' not found"); } v3s16 loaded_p(stoi(px_raw), stoi(py_raw), stoi(pz_raw)); // Old node std::string old_name; try{ old_name = deSerializeJsonString(is); }catch(SerializationError &e){ errorstream<<"Serialization error in RollbackAction::fromStream(): " <<"old_name: "<<e.what()<<std::endl; throw e; } c = is.get(); if(c != ' '){ is.putback(c); throw SerializationError("RollbackAction: after-old_name ' ' not found"); } std::string old_p1_raw; std::string old_p2_raw; std::getline(is, old_p1_raw, ' '); std::getline(is, old_p2_raw, ' '); int old_p1 = stoi(old_p1_raw); int old_p2 = stoi(old_p2_raw); std::string old_meta; try{ old_meta = deSerializeJsonString(is); }catch(SerializationError &e){ errorstream<<"Serialization error in RollbackAction::fromStream(): " <<"old_meta: "<<e.what()<<std::endl; throw e; } c = is.get(); if(c != ' '){ is.putback(c); throw SerializationError("RollbackAction: after-old_meta ' ' not found"); } // New node std::string new_name; try{ new_name = deSerializeJsonString(is); }catch(SerializationError &e){ errorstream<<"Serialization error in RollbackAction::fromStream(): " <<"new_name: "<<e.what()<<std::endl; throw e; } c = is.get(); if(c != ' '){ is.putback(c); throw SerializationError("RollbackAction: after-new_name ' ' not found"); } std::string new_p1_raw; std::string new_p2_raw; std::getline(is, new_p1_raw, ' '); std::getline(is, new_p2_raw, ' '); int new_p1 = stoi(new_p1_raw); int new_p2 = stoi(new_p2_raw); std::string new_meta; try{ new_meta = deSerializeJsonString(is); }catch(SerializationError &e){ errorstream<<"Serialization error in RollbackAction::fromStream(): " <<"new_meta: "<<e.what()<<std::endl; throw e; } c = is.get(); if(c != ']'){ is.putback(c); throw SerializationError("RollbackAction: after-new_meta ] not found"); } // Set values type = TYPE_SET_NODE; p = loaded_p; n_old.name = old_name; n_old.param1 = old_p1; n_old.param2 = old_p2; n_old.meta = old_meta; n_new.name = new_name; n_new.param1 = new_p1; n_new.param2 = new_p2; n_new.meta = new_meta; } else if(id == "modify_inventory_stack") { // Location std::string location; try{ location = deSerializeJsonString(is); }catch(SerializationError &e){ errorstream<<"Serialization error in RollbackAction::fromStream(): " <<"location: "<<e.what()<<std::endl; throw e; } c = is.get(); if(c != ' '){ is.putback(c); throw SerializationError("RollbackAction: after-loc ' ' not found"); } // List std::string listname; try{ listname = deSerializeJsonString(is); }catch(SerializationError &e){ errorstream<<"Serialization error in RollbackAction::fromStream(): " <<"listname: "<<e.what()<<std::endl; throw e; } c = is.get(); if(c != ' '){ is.putback(c); throw SerializationError("RollbackAction: after-list ' ' not found"); } // Index std::string index_raw; std::getline(is, index_raw, ' '); // add/remove std::string addremove; std::getline(is, addremove, ' '); if(addremove != "add" && addremove != "remove"){ throw SerializationError("RollbackAction: addremove is not add or remove"); } // Itemstring std::string stack; try{ stack = deSerializeJsonString(is); }catch(SerializationError &e){ errorstream<<"Serialization error in RollbackAction::fromStream(): " <<"stack: "<<e.what()<<std::endl; throw e; } // Set values type = TYPE_MODIFY_INVENTORY_STACK; inventory_location = location; inventory_list = listname; inventory_index = stoi(index_raw); inventory_add = (addremove == "add"); inventory_stack = stack; } else { throw SerializationError("RollbackAction: Unknown id"); } } bool RollbackAction::isImportant(IGameDef *gamedef) const { switch(type){ case TYPE_SET_NODE: { // If names differ, action is always important if(n_old.name != n_new.name) return true; // If metadata differs, action is always important if(n_old.meta != n_new.meta) return true; INodeDefManager *ndef = gamedef->ndef(); // Both are of the same name, so a single definition is needed const ContentFeatures &def = ndef->get(n_old.name); // If the type is flowing liquid, action is not important if(def.liquid_type == LIQUID_FLOWING) return false; // Otherwise action is important return true; } default: return true; } } bool RollbackAction::getPosition(v3s16 *dst) const { switch(type){ case RollbackAction::TYPE_SET_NODE: if(dst) *dst = p; return true; case RollbackAction::TYPE_MODIFY_INVENTORY_STACK: { InventoryLocation loc; loc.deSerialize(inventory_location); if(loc.type != InventoryLocation::NODEMETA) return false; if(dst) *dst = loc.p; return true; } default: return false; } } bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const { try{ switch(type){ case TYPE_NOTHING: return true; case TYPE_SET_NODE: { INodeDefManager *ndef = gamedef->ndef(); // Make sure position is loaded from disk map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false); // Check current node MapNode current_node = map->getNodeNoEx(p); std::string current_name = ndef->get(current_node).name; // If current node not the new node, it's bad if(current_name != n_new.name) return false; /*// If current node not the new node and not ignore, it's bad if(current_name != n_new.name && current_name != "ignore") return false;*/ // Create rollback node MapNode n(ndef, n_old.name, n_old.param1, n_old.param2); // Set rollback node try{ if(!map->addNodeWithEvent(p, n)){ infostream<<"RollbackAction::applyRevert(): " <<"AddNodeWithEvent failed at " <<PP(p)<<" for "<<n_old.name<<std::endl; return false; } NodeMetadata *meta = map->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: "<<e.what()<<std::endl; return false; } // Success return true; } case TYPE_MODIFY_INVENTORY_STACK: { InventoryLocation loc; loc.deSerialize(inventory_location); ItemStack stack; stack.deSerialize(inventory_stack, gamedef->idef()); Inventory *inv = imgr->getInventory(loc); if(!inv){ infostream<<"RollbackAction::applyRevert(): Could not get " "inventory at "<<inventory_location<<std::endl; return false; } InventoryList *list = inv->getList(inventory_list); if(!list){ infostream<<"RollbackAction::applyRevert(): Could not get " "inventory list \""<<inventory_list<<"\" in " <<inventory_location<<std::endl; return false; } if(list->getSize() <= inventory_index){ infostream<<"RollbackAction::applyRevert(): List index " <<inventory_index<<" too large in " <<"inventory list \""<<inventory_list<<"\" in " <<inventory_location<<std::endl; } // If item was added, take away item, otherwise add removed item if(inventory_add){ // Silently ignore different current item if(list->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" <<std::endl; return false; } }catch(SerializationError &e){ errorstream<<"RollbackAction::applyRevert(): n_old.name="<<n_old.name <<", SerializationError: "<<e.what()<<std::endl; } return false; }