0c8aef9ab5137afabe81c6474a730f6dddc00f2e
[oweals/minetest.git] / src / rollback_interface.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "rollback_interface.h"
21 #include <sstream>
22 #include "util/serialize.h"
23 #include "util/string.h"
24 #include "util/numeric.h"
25 #include "util/basic_macros.h"
26 #include "map.h"
27 #include "gamedef.h"
28 #include "nodedef.h"
29 #include "nodemetadata.h"
30 #include "exceptions.h"
31 #include "log.h"
32 #include "inventorymanager.h"
33 #include "inventory.h"
34 #include "mapblock.h"
35
36
37 RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef)
38 {
39         const NodeDefManager *ndef = gamedef->ndef();
40         MapNode n = map->getNode(p);
41         name = ndef->get(n).name;
42         param1 = n.param1;
43         param2 = n.param2;
44         NodeMetadata *metap = map->getNodeMetadata(p);
45         if (metap) {
46                 std::ostringstream os(std::ios::binary);
47                 metap->serialize(os, 1); // FIXME: version bump??
48                 meta = os.str();
49         }
50 }
51
52
53 std::string RollbackAction::toString() const
54 {
55         std::ostringstream os(std::ios::binary);
56         switch (type) {
57         case TYPE_SET_NODE:
58                 os << "set_node " << PP(p);
59                 os << ": (" << serializeJsonString(n_old.name);
60                 os << ", " << itos(n_old.param1);
61                 os << ", " << itos(n_old.param2);
62                 os << ", " << serializeJsonString(n_old.meta);
63                 os << ") -> (" << serializeJsonString(n_new.name);
64                 os << ", " << itos(n_new.param1);
65                 os << ", " << itos(n_new.param2);
66                 os << ", " << serializeJsonString(n_new.meta);
67                 os << ')';
68         case TYPE_MODIFY_INVENTORY_STACK:
69                 os << "modify_inventory_stack (";
70                 os << serializeJsonString(inventory_location);
71                 os << ", " << serializeJsonString(inventory_list);
72                 os << ", " << inventory_index;
73                 os << ", " << (inventory_add ? "add" : "remove");
74                 os << ", " << serializeJsonString(inventory_stack.getItemString());
75                 os << ')';
76         default:
77                 return "<unknown action>";
78         }
79         return os.str();
80 }
81
82
83 bool RollbackAction::isImportant(IGameDef *gamedef) const
84 {
85         if (type != TYPE_SET_NODE)
86                 return true;
87         // If names differ, action is always important
88         if(n_old.name != n_new.name)
89                 return true;
90         // If metadata differs, action is always important
91         if(n_old.meta != n_new.meta)
92                 return true;
93         const NodeDefManager *ndef = gamedef->ndef();
94         // Both are of the same name, so a single definition is needed
95         const ContentFeatures &def = ndef->get(n_old.name);
96         // If the type is flowing liquid, action is not important
97         if (def.liquid_type == LIQUID_FLOWING)
98                 return false;
99         // Otherwise action is important
100         return true;
101 }
102
103
104 bool RollbackAction::getPosition(v3s16 *dst) const
105 {
106         switch (type) {
107         case TYPE_SET_NODE:
108                 if (dst) *dst = p;
109                 return true;
110         case TYPE_MODIFY_INVENTORY_STACK: {
111                 InventoryLocation loc;
112                 loc.deSerialize(inventory_location);
113                 if (loc.type != InventoryLocation::NODEMETA) {
114                         return false;
115                 }
116                 if (dst) *dst = loc.p;
117                 return true; }
118         default:
119                 return false;
120         }
121 }
122
123
124 bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const
125 {
126         try {
127                 switch (type) {
128                 case TYPE_NOTHING:
129                         return true;
130                 case TYPE_SET_NODE: {
131                         const NodeDefManager *ndef = gamedef->ndef();
132                         // Make sure position is loaded from disk
133                         map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false);
134                         // Check current node
135                         MapNode current_node = map->getNode(p);
136                         std::string current_name = ndef->get(current_node).name;
137                         // If current node not the new node, it's bad
138                         if (current_name != n_new.name) {
139                                 return false;
140                         }
141                         // Create rollback node
142                         content_t id = CONTENT_IGNORE;
143                         if (!ndef->getId(n_old.name, id)) {
144                                 // The old node is not registered
145                                 return false;
146                         }
147                         MapNode n(id, n_old.param1, n_old.param2);
148                         // Set rollback node
149                         try {
150                                 if (!map->addNodeWithEvent(p, n)) {
151                                         infostream << "RollbackAction::applyRevert(): "
152                                                 << "AddNodeWithEvent failed at "
153                                                 << PP(p) << " for " << n_old.name
154                                                 << std::endl;
155                                         return false;
156                                 }
157                                 if (n_old.meta.empty()) {
158                                         map->removeNodeMetadata(p);
159                                 } else {
160                                         NodeMetadata *meta = map->getNodeMetadata(p);
161                                         if (!meta) {
162                                                 meta = new NodeMetadata(gamedef->idef());
163                                                 if (!map->setNodeMetadata(p, meta)) {
164                                                         delete meta;
165                                                         infostream << "RollbackAction::applyRevert(): "
166                                                                 << "setNodeMetadata failed at "
167                                                                 << PP(p) << " for " << n_old.name
168                                                                 << std::endl;
169                                                         return false;
170                                                 }
171                                         }
172                                         std::istringstream is(n_old.meta, std::ios::binary);
173                                         meta->deSerialize(is, 1); // FIXME: version bump??
174                                 }
175                                 // Inform other things that the meta data has changed
176                                 MapEditEvent event;
177                                 event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
178                                 event.p = p;
179                                 map->dispatchEvent(&event);
180                         } catch (InvalidPositionException &e) {
181                                 infostream << "RollbackAction::applyRevert(): "
182                                         << "InvalidPositionException: " << e.what()
183                                         << std::endl;
184                                 return false;
185                         }
186                         // Success
187                         return true; }
188                 case TYPE_MODIFY_INVENTORY_STACK: {
189                         InventoryLocation loc;
190                         loc.deSerialize(inventory_location);
191                         Inventory *inv = imgr->getInventory(loc);
192                         if (!inv) {
193                                 infostream << "RollbackAction::applyRevert(): Could not get "
194                                         "inventory at " << inventory_location << std::endl;
195                                 return false;
196                         }
197                         InventoryList *list = inv->getList(inventory_list);
198                         if (!list) {
199                                 infostream << "RollbackAction::applyRevert(): Could not get "
200                                         "inventory list \"" << inventory_list << "\" in "
201                                         << inventory_location << std::endl;
202                                 return false;
203                         }
204                         if (list->getSize() <= inventory_index) {
205                                 infostream << "RollbackAction::applyRevert(): List index "
206                                         << inventory_index << " too large in "
207                                         << "inventory list \"" << inventory_list << "\" in "
208                                         << inventory_location << std::endl;
209                                 return false;
210                         }
211
212                         // If item was added, take away item, otherwise add removed item
213                         if (inventory_add) {
214                                 // Silently ignore different current item
215                                 if (list->getItem(inventory_index).name !=
216                                                 gamedef->idef()->getAlias(inventory_stack.name))
217                                         return false;
218                                 list->takeItem(inventory_index, inventory_stack.count);
219                         } else {
220                                 list->addItem(inventory_index, inventory_stack);
221                         }
222                         // Inventory was modified; send to clients
223                         imgr->setInventoryModified(loc);
224                         return true; }
225                 default:
226                         errorstream << "RollbackAction::applyRevert(): type not handled"
227                                 << std::endl;
228                         return false;
229                 }
230         } catch(SerializationError &e) {
231                 errorstream << "RollbackAction::applyRevert(): n_old.name=" << n_old.name
232                                 << ", SerializationError: " << e.what() << std::endl;
233         }
234         return false;
235 }
236