]> git.lizzy.rs Git - minetest.git/blob - src/rollback_interface.cpp
b3f457029bd74e299f9634f1fa4568034993347e
[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 "map.h"
26 #include "gamedef.h"
27 #include "nodedef.h"
28 #include "nodemetadata.h"
29 #include "exceptions.h"
30 #include "log.h"
31 #include "inventorymanager.h"
32 #include "inventory.h"
33 #include "mapblock.h"
34
35 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
36
37
38 RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef)
39 {
40         INodeDefManager *ndef = gamedef->ndef();
41         MapNode n = map->getNodeNoEx(p);
42         name = ndef->get(n).name;
43         param1 = n.param1;
44         param2 = n.param2;
45         NodeMetadata *metap = map->getNodeMetadata(p);
46         if (metap) {
47                 std::ostringstream os(std::ios::binary);
48                 metap->serialize(os);
49                 meta = os.str();
50         }
51 }
52
53
54 std::string RollbackAction::toString() const
55 {
56         std::ostringstream os(std::ios::binary);
57         switch (type) {
58         case TYPE_SET_NODE:
59                 os << "set_node " << PP(p);
60                 os << ": (" << serializeJsonString(n_old.name);
61                 os << ", " << itos(n_old.param1);
62                 os << ", " << itos(n_old.param2);
63                 os << ", " << serializeJsonString(n_old.meta);
64                 os << ") -> (" << serializeJsonString(n_new.name);
65                 os << ", " << itos(n_new.param1);
66                 os << ", " << itos(n_new.param2);
67                 os << ", " << serializeJsonString(n_new.meta);
68                 os << ')';
69         case TYPE_MODIFY_INVENTORY_STACK:
70                 os << "modify_inventory_stack (";
71                 os << serializeJsonString(inventory_location);
72                 os << ", " << serializeJsonString(inventory_list);
73                 os << ", " << inventory_index;
74                 os << ", " << (inventory_add ? "add" : "remove");
75                 os << ", " << serializeJsonString(inventory_stack.getItemString());
76                 os << ')';
77         default:
78                 return "<unknown action>";
79         }
80         return os.str();
81 }
82
83
84 bool RollbackAction::isImportant(IGameDef *gamedef) const
85 {
86         if (type != TYPE_SET_NODE)
87                 return true;
88         // If names differ, action is always important
89         if(n_old.name != n_new.name)
90                 return true;
91         // If metadata differs, action is always important
92         if(n_old.meta != n_new.meta)
93                 return true;
94         INodeDefManager *ndef = gamedef->ndef();
95         // Both are of the same name, so a single definition is needed
96         const ContentFeatures &def = ndef->get(n_old.name);
97         // If the type is flowing liquid, action is not important
98         if (def.liquid_type == LIQUID_FLOWING)
99                 return false;
100         // Otherwise action is important
101         return true;
102 }
103
104
105 bool RollbackAction::getPosition(v3s16 *dst) const
106 {
107         switch (type) {
108         case TYPE_SET_NODE:
109                 if (dst) *dst = p;
110                 return true;
111         case TYPE_MODIFY_INVENTORY_STACK: {
112                 InventoryLocation loc;
113                 loc.deSerialize(inventory_location);
114                 if (loc.type != InventoryLocation::NODEMETA) {
115                         return false;
116                 }
117                 if (dst) *dst = loc.p;
118                 return true; }
119         default:
120                 return false;
121         }
122 }
123
124
125 bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const
126 {
127         try {
128                 switch (type) {
129                 case TYPE_NOTHING:
130                         return true;
131                 case TYPE_SET_NODE: {
132                         INodeDefManager *ndef = gamedef->ndef();
133                         // Make sure position is loaded from disk
134                         map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false);
135                         // Check current node
136                         MapNode current_node = map->getNodeNoEx(p);
137                         std::string current_name = ndef->get(current_node).name;
138                         // If current node not the new node, it's bad
139                         if (current_name != n_new.name) {
140                                 return false;
141                         }
142                         // Create rollback node
143                         MapNode n(ndef, n_old.name, n_old.param1, n_old.param2);
144                         // Set rollback node
145                         try {
146                                 if (!map->addNodeWithEvent(p, n)) {
147                                         infostream << "RollbackAction::applyRevert(): "
148                                                 << "AddNodeWithEvent failed at "
149                                                 << PP(p) << " for " << n_old.name
150                                                 << std::endl;
151                                         return false;
152                                 }
153                                 if (n_old.meta.empty()) {
154                                         map->removeNodeMetadata(p);
155                                 } else {
156                                         NodeMetadata *meta = map->getNodeMetadata(p);
157                                         if (!meta) {
158                                                 meta = new NodeMetadata(gamedef);
159                                                 if (!map->setNodeMetadata(p, meta)) {
160                                                         delete meta;
161                                                         infostream << "RollbackAction::applyRevert(): "
162                                                                 << "setNodeMetadata failed at "
163                                                                 << PP(p) << " for " << n_old.name
164                                                                 << std::endl;
165                                                         return false;
166                                                 }
167                                         }
168                                         std::istringstream is(n_old.meta, std::ios::binary);
169                                         meta->deSerialize(is);
170                                 }
171                                 // Inform other things that the meta data has changed
172                                 v3s16 blockpos = getContainerPos(p, MAP_BLOCKSIZE);
173                                 MapEditEvent event;
174                                 event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
175                                 event.p = blockpos;
176                                 map->dispatchEvent(&event);
177                                 // Set the block to be saved
178                                 MapBlock *block = map->getBlockNoCreateNoEx(blockpos);
179                                 if (block) {
180                                         block->raiseModified(MOD_STATE_WRITE_NEEDED,
181                                                 MOD_REASON_REPORT_META_CHANGE);
182                                 }
183                         } catch (InvalidPositionException &e) {
184                                 infostream << "RollbackAction::applyRevert(): "
185                                         << "InvalidPositionException: " << e.what()
186                                         << std::endl;
187                                 return false;
188                         }
189                         // Success
190                         return true; }
191                 case TYPE_MODIFY_INVENTORY_STACK: {
192                         InventoryLocation loc;
193                         loc.deSerialize(inventory_location);
194                         std::string real_name = gamedef->idef()->getAlias(inventory_stack.name);
195                         Inventory *inv = imgr->getInventory(loc);
196                         if (!inv) {
197                                 infostream << "RollbackAction::applyRevert(): Could not get "
198                                         "inventory at " << inventory_location << std::endl;
199                                 return false;
200                         }
201                         InventoryList *list = inv->getList(inventory_list);
202                         if (!list) {
203                                 infostream << "RollbackAction::applyRevert(): Could not get "
204                                         "inventory list \"" << inventory_list << "\" in "
205                                         << inventory_location << std::endl;
206                                 return false;
207                         }
208                         if (list->getSize() <= inventory_index) {
209                                 infostream << "RollbackAction::applyRevert(): List index "
210                                         << inventory_index << " too large in "
211                                         << "inventory list \"" << inventory_list << "\" in "
212                                         << inventory_location << std::endl;
213                                 return false;
214                         }
215                         // If item was added, take away item, otherwise add removed item
216                         if (inventory_add) {
217                                 // Silently ignore different current item
218                                 if (list->getItem(inventory_index).name != real_name)
219                                         return false;
220                                 list->takeItem(inventory_index, inventory_stack.count);
221                         } else {
222                                 list->addItem(inventory_index, inventory_stack);
223                         }
224                         // Inventory was modified; send to clients
225                         imgr->setInventoryModified(loc);
226                         return true; }
227                 default:
228                         errorstream << "RollbackAction::applyRevert(): type not handled"
229                                 << std::endl;
230                         return false;
231                 }
232         } catch(SerializationError &e) {
233                 errorstream << "RollbackAction::applyRevert(): n_old.name=" << n_old.name
234                                 << ", SerializationError: " << e.what() << std::endl;
235         }
236         return false;
237 }
238