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 
RollbackNode(Map * map,v3s16 p,IGameDef * gamedef)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 
toString() const53 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 
isImportant(IGameDef * gamedef) const83 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 
getPosition(v3s16 * dst) const104 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 
applyRevert(Map * map,InventoryManager * imgr,IGameDef * gamedef) const124 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 
237