Merge remote branch 'origin/master'
[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 "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 RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef)
38 {
39         INodeDefManager *ndef = gamedef->ndef();
40         MapNode n = map->getNodeNoEx(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);
48                 meta = os.str();
49         }
50 }
51
52 std::string RollbackAction::toString() const
53 {
54         switch(type){
55         case TYPE_SET_NODE: {
56                 std::ostringstream os(std::ios::binary);
57                 os<<"[set_node";
58                 os<<" ";
59                 os<<"("<<itos(p.X)<<","<<itos(p.Y)<<","<<itos(p.Z)<<")";
60                 os<<" ";
61                 os<<serializeJsonString(n_old.name);
62                 os<<" ";
63                 os<<itos(n_old.param1);
64                 os<<" ";
65                 os<<itos(n_old.param2);
66                 os<<" ";
67                 os<<serializeJsonString(n_old.meta);
68                 os<<" ";
69                 os<<serializeJsonString(n_new.name);
70                 os<<" ";
71                 os<<itos(n_new.param1);
72                 os<<" ";
73                 os<<itos(n_new.param2);
74                 os<<" ";
75                 os<<serializeJsonString(n_new.meta);
76                 os<<"]";
77                 return os.str(); }
78         case TYPE_MODIFY_INVENTORY_STACK: {
79                 std::ostringstream os(std::ios::binary);
80                 os<<"[modify_inventory_stack";
81                 os<<" ";
82                 os<<serializeJsonString(inventory_location);
83                 os<<" ";
84                 os<<serializeJsonString(inventory_list);
85                 os<<" ";
86                 os<<inventory_index;
87                 os<<" ";
88                 os<<(inventory_add?"add":"remove");
89                 os<<" ";
90                 os<<serializeJsonString(inventory_stack);
91                 os<<"]";
92                 return os.str(); }
93         default:
94                 return "none";
95         }
96 }
97
98 void RollbackAction::fromStream(std::istream &is) throw(SerializationError)
99 {
100         int c = is.get();
101         if(c != '['){
102                 is.putback(c);
103                 throw SerializationError("RollbackAction: starting [ not found");
104         }
105         
106         std::string id;
107         std::getline(is, id, ' ');
108         
109         if(id == "set_node")
110         {
111                 c = is.get();
112                 if(c != '('){
113                         is.putback(c);
114                         throw SerializationError("RollbackAction: starting ( not found");
115                 }
116                 // Position
117                 std::string px_raw;
118                 std::string py_raw;
119                 std::string pz_raw;
120                 std::getline(is, px_raw, ',');
121                 std::getline(is, py_raw, ',');
122                 std::getline(is, pz_raw, ')');
123                 c = is.get();
124                 if(c != ' '){
125                         is.putback(c);
126                         throw SerializationError("RollbackAction: after-p ' ' not found");
127                 }
128                 v3s16 loaded_p(stoi(px_raw), stoi(py_raw), stoi(pz_raw));
129                 // Old node
130                 std::string old_name;
131                 try{
132                         old_name = deSerializeJsonString(is);
133                 }catch(SerializationError &e){
134                         errorstream<<"Serialization error in RollbackAction::fromStream(): "
135                                         <<"old_name: "<<e.what()<<std::endl;
136                         throw e;
137                 }
138                 c = is.get();
139                 if(c != ' '){
140                         is.putback(c);
141                         throw SerializationError("RollbackAction: after-old_name ' ' not found");
142                 }
143                 std::string old_p1_raw;
144                 std::string old_p2_raw;
145                 std::getline(is, old_p1_raw, ' ');
146                 std::getline(is, old_p2_raw, ' ');
147                 int old_p1 = stoi(old_p1_raw);
148                 int old_p2 = stoi(old_p2_raw);
149                 std::string old_meta;
150                 try{
151                         old_meta = deSerializeJsonString(is);
152                 }catch(SerializationError &e){
153                         errorstream<<"Serialization error in RollbackAction::fromStream(): "
154                                         <<"old_meta: "<<e.what()<<std::endl;
155                         throw e;
156                 }
157                 c = is.get();
158                 if(c != ' '){
159                         is.putback(c);
160                         throw SerializationError("RollbackAction: after-old_meta ' ' not found");
161                 }
162                 // New node
163                 std::string new_name;
164                 try{
165                         new_name = deSerializeJsonString(is);
166                 }catch(SerializationError &e){
167                         errorstream<<"Serialization error in RollbackAction::fromStream(): "
168                                         <<"new_name: "<<e.what()<<std::endl;
169                         throw e;
170                 }
171                 c = is.get();
172                 if(c != ' '){
173                         is.putback(c);
174                         throw SerializationError("RollbackAction: after-new_name ' ' not found");
175                 }
176                 std::string new_p1_raw;
177                 std::string new_p2_raw;
178                 std::getline(is, new_p1_raw, ' ');
179                 std::getline(is, new_p2_raw, ' ');
180                 int new_p1 = stoi(new_p1_raw);
181                 int new_p2 = stoi(new_p2_raw);
182                 std::string new_meta;
183                 try{
184                         new_meta = deSerializeJsonString(is);
185                 }catch(SerializationError &e){
186                         errorstream<<"Serialization error in RollbackAction::fromStream(): "
187                                         <<"new_meta: "<<e.what()<<std::endl;
188                         throw e;
189                 }
190                 c = is.get();
191                 if(c != ']'){
192                         is.putback(c);
193                         throw SerializationError("RollbackAction: after-new_meta ] not found");
194                 }
195                 // Set values
196                 type = TYPE_SET_NODE;
197                 p = loaded_p;
198                 n_old.name = old_name;
199                 n_old.param1 = old_p1;
200                 n_old.param2 = old_p2;
201                 n_old.meta = old_meta;
202                 n_new.name = new_name;
203                 n_new.param1 = new_p1;
204                 n_new.param2 = new_p2;
205                 n_new.meta = new_meta;
206         }
207         else if(id == "modify_inventory_stack")
208         {
209                 // Location
210                 std::string location;
211                 try{
212                         location = deSerializeJsonString(is);
213                 }catch(SerializationError &e){
214                         errorstream<<"Serialization error in RollbackAction::fromStream(): "
215                                         <<"location: "<<e.what()<<std::endl;
216                         throw e;
217                 }
218                 c = is.get();
219                 if(c != ' '){
220                         is.putback(c);
221                         throw SerializationError("RollbackAction: after-loc ' ' not found");
222                 }
223                 // List
224                 std::string listname;
225                 try{
226                         listname = deSerializeJsonString(is);
227                 }catch(SerializationError &e){
228                         errorstream<<"Serialization error in RollbackAction::fromStream(): "
229                                         <<"listname: "<<e.what()<<std::endl;
230                         throw e;
231                 }
232                 c = is.get();
233                 if(c != ' '){
234                         is.putback(c);
235                         throw SerializationError("RollbackAction: after-list ' ' not found");
236                 }
237                 // Index
238                 std::string index_raw;
239                 std::getline(is, index_raw, ' ');
240                 // add/remove
241                 std::string addremove;
242                 std::getline(is, addremove, ' ');
243                 if(addremove != "add" && addremove != "remove"){
244                         throw SerializationError("RollbackAction: addremove is not add or remove");
245                 }
246                 // Itemstring
247                 std::string stack;
248                 try{
249                         stack = deSerializeJsonString(is);
250                 }catch(SerializationError &e){
251                         errorstream<<"Serialization error in RollbackAction::fromStream(): "
252                                         <<"stack: "<<e.what()<<std::endl;
253                         throw e;
254                 }
255                 // Set values
256                 type = TYPE_MODIFY_INVENTORY_STACK;
257                 inventory_location = location;
258                 inventory_list = listname;
259                 inventory_index = stoi(index_raw);
260                 inventory_add = (addremove == "add");
261                 inventory_stack = stack;
262         }
263         else
264         {
265                 throw SerializationError("RollbackAction: Unknown id");
266         }
267 }
268
269 bool RollbackAction::isImportant(IGameDef *gamedef) const
270 {
271         switch(type){
272         case TYPE_SET_NODE: {
273                 // If names differ, action is always important
274                 if(n_old.name != n_new.name)
275                         return true;
276                 // If metadata differs, action is always important
277                 if(n_old.meta != n_new.meta)
278                         return true;
279                 INodeDefManager *ndef = gamedef->ndef();
280                 // Both are of the same name, so a single definition is needed
281                 const ContentFeatures &def = ndef->get(n_old.name);
282                 // If the type is flowing liquid, action is not important
283                 if(def.liquid_type == LIQUID_FLOWING)
284                         return false;
285                 // Otherwise action is important
286                 return true; }
287         default:
288                 return true;
289         }
290 }
291
292 bool RollbackAction::getPosition(v3s16 *dst) const
293 {
294         switch(type){
295         case RollbackAction::TYPE_SET_NODE:
296                 if(dst) *dst = p;
297                 return true;
298         case RollbackAction::TYPE_MODIFY_INVENTORY_STACK: {
299                 InventoryLocation loc;
300                 loc.deSerialize(inventory_location);
301                 if(loc.type != InventoryLocation::NODEMETA)
302                         return false;
303                 if(dst) *dst = loc.p;
304                 return true; }
305         default:
306                 return false;
307         }
308 }
309
310 bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const
311 {
312         try{
313                 switch(type){
314                 case TYPE_NOTHING:
315                         return true;
316                 case TYPE_SET_NODE: {
317                         INodeDefManager *ndef = gamedef->ndef();
318                         // Make sure position is loaded from disk
319                         map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false);
320                         // Check current node
321                         MapNode current_node = map->getNodeNoEx(p);
322                         std::string current_name = ndef->get(current_node).name;
323                         // If current node not the new node, it's bad
324                         if(current_name != n_new.name)
325                                 return false;
326                         /*// If current node not the new node and not ignore, it's bad
327                         if(current_name != n_new.name && current_name != "ignore")
328                                 return false;*/
329                         // Create rollback node
330                         MapNode n(ndef, n_old.name, n_old.param1, n_old.param2);
331                         // Set rollback node
332                         try{
333                                 if(!map->addNodeWithEvent(p, n)){
334                                         infostream<<"RollbackAction::applyRevert(): "
335                                                         <<"AddNodeWithEvent failed at "
336                                                         <<PP(p)<<" for "<<n_old.name<<std::endl;
337                                         return false;
338                                 }
339                                 NodeMetadata *meta = map->getNodeMetadata(p);
340                                 if(n_old.meta != ""){
341                                         if(!meta){
342                                                 meta = new NodeMetadata(gamedef);
343                                                 map->setNodeMetadata(p, meta);
344                                         }
345                                         std::istringstream is(n_old.meta, std::ios::binary);
346                                         meta->deSerialize(is);
347                                 } else {
348                                         map->removeNodeMetadata(p);
349                                 }
350                                 // NOTE: This same code is in scriptapi.cpp
351                                 // Inform other things that the metadata has changed
352                                 v3s16 blockpos = getContainerPos(p, MAP_BLOCKSIZE);
353                                 MapEditEvent event;
354                                 event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
355                                 event.p = blockpos;
356                                 map->dispatchEvent(&event);
357                                 // Set the block to be saved
358                                 MapBlock *block = map->getBlockNoCreateNoEx(blockpos);
359                                 if(block)
360                                         block->raiseModified(MOD_STATE_WRITE_NEEDED,
361                                                         "NodeMetaRef::reportMetadataChange");
362                         }catch(InvalidPositionException &e){
363                                 infostream<<"RollbackAction::applyRevert(): "
364                                                 <<"InvalidPositionException: "<<e.what()<<std::endl;
365                                 return false;
366                         }
367                         // Success
368                         return true; }
369                 case TYPE_MODIFY_INVENTORY_STACK: {
370                         InventoryLocation loc;
371                         loc.deSerialize(inventory_location);
372                         ItemStack stack;
373                         stack.deSerialize(inventory_stack, gamedef->idef());
374                         Inventory *inv = imgr->getInventory(loc);
375                         if(!inv){
376                                 infostream<<"RollbackAction::applyRevert(): Could not get "
377                                                 "inventory at "<<inventory_location<<std::endl;
378                                 return false;
379                         }
380                         InventoryList *list = inv->getList(inventory_list);
381                         if(!list){
382                                 infostream<<"RollbackAction::applyRevert(): Could not get "
383                                                 "inventory list \""<<inventory_list<<"\" in "
384                                                 <<inventory_location<<std::endl;
385                                 return false;
386                         }
387                         if(list->getSize() <= inventory_index){
388                                 infostream<<"RollbackAction::applyRevert(): List index "
389                                                 <<inventory_index<<" too large in "
390                                                 <<"inventory list \""<<inventory_list<<"\" in "
391                                                 <<inventory_location<<std::endl;
392                         }
393                         // If item was added, take away item, otherwise add removed item
394                         if(inventory_add){
395                                 // Silently ignore different current item
396                                 if(list->getItem(inventory_index).name != stack.name)
397                                         return false;
398                                 list->takeItem(inventory_index, stack.count);
399                         } else {
400                                 list->addItem(inventory_index, stack);
401                         }
402                         // Inventory was modified; send to clients
403                         imgr->setInventoryModified(loc);
404                         return true; }
405                 default:
406                         errorstream<<"RollbackAction::applyRevert(): type not handled"
407                                         <<std::endl;
408                         return false;
409                 }
410         }catch(SerializationError &e){
411                 errorstream<<"RollbackAction::applyRevert(): n_old.name="<<n_old.name
412                                 <<", SerializationError: "<<e.what()<<std::endl;
413         }
414         return false;
415 }
416