1 /*
2 Minetest
3 Copyright (C) 2010-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 "inventorymanager.h"
21 #include "debug.h"
22 #include "log.h"
23 #include "serverenvironment.h"
24 #include "scripting_server.h"
25 #include "server/serveractiveobject.h"
26 #include "settings.h"
27 #include "craftdef.h"
28 #include "rollback_interface.h"
29 #include "util/strfnd.h"
30 #include "util/basic_macros.h"
31 
32 #define PLAYER_TO_SA(p)   p->getEnv()->getScriptIface()
33 
34 /*
35 	InventoryLocation
36 */
37 
dump() const38 std::string InventoryLocation::dump() const
39 {
40 	std::ostringstream os(std::ios::binary);
41 	serialize(os);
42 	return os.str();
43 }
44 
serialize(std::ostream & os) const45 void InventoryLocation::serialize(std::ostream &os) const
46 {
47 	switch (type) {
48 	case InventoryLocation::UNDEFINED:
49 		os<<"undefined";
50 		break;
51 	case InventoryLocation::CURRENT_PLAYER:
52 		os<<"current_player";
53 		break;
54 	case InventoryLocation::PLAYER:
55 		os<<"player:"<<name;
56 		break;
57 	case InventoryLocation::NODEMETA:
58 		os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
59 		break;
60 	case InventoryLocation::DETACHED:
61 		os<<"detached:"<<name;
62 		break;
63 	default:
64 		FATAL_ERROR("Unhandled inventory location type");
65 	}
66 }
67 
deSerialize(std::istream & is)68 void InventoryLocation::deSerialize(std::istream &is)
69 {
70 	std::string tname;
71 	std::getline(is, tname, ':');
72 	if (tname == "undefined") {
73 		type = InventoryLocation::UNDEFINED;
74 	} else if (tname == "current_player") {
75 		type = InventoryLocation::CURRENT_PLAYER;
76 	} else if (tname == "player") {
77 		type = InventoryLocation::PLAYER;
78 		std::getline(is, name, '\n');
79 	} else if (tname == "nodemeta") {
80 		type = InventoryLocation::NODEMETA;
81 		std::string pos;
82 		std::getline(is, pos, '\n');
83 		Strfnd fn(pos);
84 		p.X = stoi(fn.next(","));
85 		p.Y = stoi(fn.next(","));
86 		p.Z = stoi(fn.next(","));
87 	} else if (tname == "detached") {
88 		type = InventoryLocation::DETACHED;
89 		std::getline(is, name, '\n');
90 	} else {
91 		infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
92 		throw SerializationError("Unknown InventoryLocation type");
93 	}
94 }
95 
deSerialize(const std::string & s)96 void InventoryLocation::deSerialize(const std::string &s)
97 {
98 	std::istringstream is(s, std::ios::binary);
99 	deSerialize(is);
100 }
101 
102 /*
103 	InventoryAction
104 */
105 
deSerialize(std::istream & is)106 InventoryAction *InventoryAction::deSerialize(std::istream &is)
107 {
108 	std::string type;
109 	std::getline(is, type, ' ');
110 
111 	InventoryAction *a = nullptr;
112 
113 	if (type == "Move") {
114 		a = new IMoveAction(is, false);
115 	} else if (type == "MoveSomewhere") {
116 		a = new IMoveAction(is, true);
117 	} else if (type == "Drop") {
118 		a = new IDropAction(is);
119 	} else if (type == "Craft") {
120 		a = new ICraftAction(is);
121 	}
122 
123 	return a;
124 }
125 
126 /*
127 	IMoveAction
128 */
129 
IMoveAction(std::istream & is,bool somewhere)130 IMoveAction::IMoveAction(std::istream &is, bool somewhere) :
131 		move_somewhere(somewhere)
132 {
133 	std::string ts;
134 
135 	std::getline(is, ts, ' ');
136 	count = stoi(ts);
137 
138 	std::getline(is, ts, ' ');
139 	from_inv.deSerialize(ts);
140 
141 	std::getline(is, from_list, ' ');
142 
143 	std::getline(is, ts, ' ');
144 	from_i = stoi(ts);
145 
146 	std::getline(is, ts, ' ');
147 	to_inv.deSerialize(ts);
148 
149 	std::getline(is, to_list, ' ');
150 
151 	if (!somewhere) {
152 		std::getline(is, ts, ' ');
153 		to_i = stoi(ts);
154 	}
155 }
156 
swapDirections()157 void IMoveAction::swapDirections()
158 {
159 	std::swap(from_inv, to_inv);
160 	std::swap(from_list, to_list);
161 	std::swap(from_i, to_i);
162 }
163 
onPutAndOnTake(const ItemStack & src_item,ServerActiveObject * player) const164 void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const
165 {
166 	ServerScripting *sa = PLAYER_TO_SA(player);
167 	if (to_inv.type == InventoryLocation::DETACHED)
168 		sa->detached_inventory_OnPut(*this, src_item, player);
169 	else if (to_inv.type == InventoryLocation::NODEMETA)
170 		sa->nodemeta_inventory_OnPut(*this, src_item, player);
171 	else if (to_inv.type == InventoryLocation::PLAYER)
172 		sa->player_inventory_OnPut(*this, src_item, player);
173 	else
174 		assert(false);
175 
176 	if (from_inv.type == InventoryLocation::DETACHED)
177 		sa->detached_inventory_OnTake(*this, src_item, player);
178 	else if (from_inv.type == InventoryLocation::NODEMETA)
179 		sa->nodemeta_inventory_OnTake(*this, src_item, player);
180 	else if (from_inv.type == InventoryLocation::PLAYER)
181 		sa->player_inventory_OnTake(*this, src_item, player);
182 	else
183 		assert(false);
184 }
185 
onMove(int count,ServerActiveObject * player) const186 void IMoveAction::onMove(int count, ServerActiveObject *player) const
187 {
188 	ServerScripting *sa = PLAYER_TO_SA(player);
189 	if (from_inv.type == InventoryLocation::DETACHED)
190 		sa->detached_inventory_OnMove(*this, count, player);
191 	else if (from_inv.type == InventoryLocation::NODEMETA)
192 		sa->nodemeta_inventory_OnMove(*this, count, player);
193 	else if (from_inv.type == InventoryLocation::PLAYER)
194 		sa->player_inventory_OnMove(*this, count, player);
195 	else
196 		assert(false);
197 }
198 
allowPut(const ItemStack & dst_item,ServerActiveObject * player) const199 int IMoveAction::allowPut(const ItemStack &dst_item, ServerActiveObject *player) const
200 {
201 	ServerScripting *sa = PLAYER_TO_SA(player);
202 	int dst_can_put_count = 0xffff;
203 	if (to_inv.type == InventoryLocation::DETACHED)
204 		dst_can_put_count = sa->detached_inventory_AllowPut(*this, dst_item, player);
205 	else if (to_inv.type == InventoryLocation::NODEMETA)
206 		dst_can_put_count = sa->nodemeta_inventory_AllowPut(*this, dst_item, player);
207 	else if (to_inv.type == InventoryLocation::PLAYER)
208 		dst_can_put_count = sa->player_inventory_AllowPut(*this, dst_item, player);
209 	else
210 		assert(false);
211 	return dst_can_put_count;
212 }
213 
allowTake(const ItemStack & src_item,ServerActiveObject * player) const214 int IMoveAction::allowTake(const ItemStack &src_item, ServerActiveObject *player) const
215 {
216 	ServerScripting *sa = PLAYER_TO_SA(player);
217 	int src_can_take_count = 0xffff;
218 	if (from_inv.type == InventoryLocation::DETACHED)
219 		src_can_take_count = sa->detached_inventory_AllowTake(*this, src_item, player);
220 	else if (from_inv.type == InventoryLocation::NODEMETA)
221 		src_can_take_count = sa->nodemeta_inventory_AllowTake(*this, src_item, player);
222 	else if (from_inv.type == InventoryLocation::PLAYER)
223 		src_can_take_count = sa->player_inventory_AllowTake(*this, src_item, player);
224 	else
225 		assert(false);
226 	return src_can_take_count;
227 }
228 
allowMove(int try_take_count,ServerActiveObject * player) const229 int IMoveAction::allowMove(int try_take_count, ServerActiveObject *player) const
230 {
231 	ServerScripting *sa = PLAYER_TO_SA(player);
232 	int src_can_take_count = 0xffff;
233 	if (from_inv.type == InventoryLocation::DETACHED)
234 		src_can_take_count = sa->detached_inventory_AllowMove(*this, try_take_count, player);
235 	else if (from_inv.type == InventoryLocation::NODEMETA)
236 		src_can_take_count = sa->nodemeta_inventory_AllowMove(*this, try_take_count, player);
237 	else if (from_inv.type == InventoryLocation::PLAYER)
238 		src_can_take_count = sa->player_inventory_AllowMove(*this, try_take_count, player);
239 	else
240 		assert(false);
241 	return src_can_take_count;
242 }
243 
apply(InventoryManager * mgr,ServerActiveObject * player,IGameDef * gamedef)244 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
245 {
246 	Inventory *inv_from = mgr->getInventory(from_inv);
247 	Inventory *inv_to = mgr->getInventory(to_inv);
248 
249 	if (!inv_from) {
250 		infostream << "IMoveAction::apply(): FAIL: source inventory not found: "
251 			<< "from_inv=\""<<from_inv.dump() << "\""
252 			<< ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
253 		return;
254 	}
255 	if (!inv_to) {
256 		infostream << "IMoveAction::apply(): FAIL: destination inventory not found: "
257 			<< "from_inv=\"" << from_inv.dump() << "\""
258 			<< ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
259 		return;
260 	}
261 
262 	InventoryList *list_from = inv_from->getList(from_list);
263 	InventoryList *list_to = inv_to->getList(to_list);
264 
265 	/*
266 		If a list doesn't exist or the source item doesn't exist
267 	*/
268 	if (!list_from) {
269 		infostream << "IMoveAction::apply(): FAIL: source list not found: "
270 			<< "from_inv=\"" << from_inv.dump() << "\""
271 			<< ", from_list=\"" << from_list << "\"" << std::endl;
272 		return;
273 	}
274 	if (!list_to) {
275 		infostream << "IMoveAction::apply(): FAIL: destination list not found: "
276 			<< "to_inv=\""<<to_inv.dump() << "\""
277 			<< ", to_list=\"" << to_list << "\"" << std::endl;
278 		return;
279 	}
280 
281 	if (move_somewhere) {
282 		s16 old_to_i = to_i;
283 		u16 old_count = count;
284 		caused_by_move_somewhere = true;
285 		move_somewhere = false;
286 
287 		infostream << "IMoveAction::apply(): moving item somewhere"
288 			<< " msom=" << move_somewhere
289 			<< " count=" << count
290 			<< " from inv=\"" << from_inv.dump() << "\""
291 			<< " list=\"" << from_list << "\""
292 			<< " i=" << from_i
293 			<< " to inv=\"" << to_inv.dump() << "\""
294 			<< " list=\"" << to_list << "\""
295 			<< std::endl;
296 
297 		// Try to add the item to destination list
298 		s16 dest_size = list_to->getSize();
299 		// First try all the non-empty slots
300 		for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
301 			if (!list_to->getItem(dest_i).empty()) {
302 				to_i = dest_i;
303 				apply(mgr, player, gamedef);
304 				assert(move_count <= count);
305 				count -= move_count;
306 			}
307 		}
308 
309 		// Then try all the empty ones
310 		for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
311 			if (list_to->getItem(dest_i).empty()) {
312 				to_i = dest_i;
313 				apply(mgr, player, gamedef);
314 				count -= move_count;
315 			}
316 		}
317 
318 		to_i = old_to_i;
319 		count = old_count;
320 		caused_by_move_somewhere = false;
321 		move_somewhere = true;
322 		return;
323 	}
324 
325 	if ((u16)to_i > list_to->getSize()) {
326 		infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
327 			<< "to_i=" << to_i
328 			<< ", size=" << list_to->getSize() << std::endl;
329 		return;
330 	}
331 	/*
332 		Do not handle rollback if both inventories are that of the same player
333 	*/
334 	bool ignore_rollback = (
335 		from_inv.type == InventoryLocation::PLAYER &&
336 		from_inv == to_inv);
337 
338 	/*
339 		Collect information of endpoints
340 	*/
341 
342 	ItemStack src_item = list_from->getItem(from_i);
343 	if (count > 0 && count < src_item.count)
344 		src_item.count = count;
345 	if (src_item.empty())
346 		return;
347 
348 	int src_can_take_count = 0xffff;
349 	int dst_can_put_count = 0xffff;
350 
351 	// this is needed for swapping items inside one inventory to work
352 	ItemStack restitem;
353 	bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
354 		&& restitem.count == src_item.count
355 		&& !caused_by_move_somewhere;
356 	move_count = src_item.count - restitem.count;
357 
358 	// Shift-click: Cannot fill this stack, proceed with next slot
359 	if (caused_by_move_somewhere && move_count == 0) {
360 		return;
361 	}
362 
363 	if (allow_swap) {
364 		// Swap will affect the entire stack if it can performed.
365 		src_item = list_from->getItem(from_i);
366 		count = src_item.count;
367 	}
368 
369 	if (from_inv == to_inv) {
370 		// Move action within the same inventory
371 		src_can_take_count = allowMove(src_item.count, player);
372 
373 		bool swap_expected = allow_swap;
374 		allow_swap = allow_swap
375 			&& (src_can_take_count == -1 || src_can_take_count >= src_item.count);
376 		if (allow_swap) {
377 			int try_put_count = list_to->getItem(to_i).count;
378 			swapDirections();
379 			dst_can_put_count = allowMove(try_put_count, player);
380 			allow_swap = allow_swap
381 				&& (dst_can_put_count == -1 || dst_can_put_count >= try_put_count);
382 			swapDirections();
383 		} else {
384 			dst_can_put_count = src_can_take_count;
385 		}
386 		if (swap_expected != allow_swap)
387 			src_can_take_count = dst_can_put_count = 0;
388 	} else {
389 		// Take from one inventory, put into another
390 		int src_item_count = src_item.count;
391 		if (caused_by_move_somewhere)
392 			// When moving somewhere: temporarily use the actual movable stack
393 			// size to ensure correct callback execution.
394 			src_item.count = move_count;
395 		dst_can_put_count = allowPut(src_item, player);
396 		src_can_take_count = allowTake(src_item, player);
397 		if (caused_by_move_somewhere)
398 			// Reset source item count
399 			src_item.count = src_item_count;
400 		bool swap_expected = allow_swap;
401 		allow_swap = allow_swap
402 			&& (src_can_take_count == -1 || src_can_take_count >= src_item.count)
403 			&& (dst_can_put_count == -1 || dst_can_put_count >= src_item.count);
404 		// A swap is expected, which means that we have to
405 		// run the "allow" callbacks a second time with swapped inventories
406 		if (allow_swap) {
407 			ItemStack dst_item = list_to->getItem(to_i);
408 			swapDirections();
409 
410 			int src_can_take = allowPut(dst_item, player);
411 			int dst_can_put = allowTake(dst_item, player);
412 			allow_swap = allow_swap
413 				&& (src_can_take == -1 || src_can_take >= dst_item.count)
414 				&& (dst_can_put == -1 || dst_can_put >= dst_item.count);
415 			swapDirections();
416 		}
417 		if (swap_expected != allow_swap)
418 			src_can_take_count = dst_can_put_count = 0;
419 	}
420 
421 	int old_count = count;
422 
423 	/* Modify count according to collected data */
424 	count = src_item.count;
425 	if (src_can_take_count != -1 && count > src_can_take_count)
426 		count = src_can_take_count;
427 	if (dst_can_put_count != -1 && count > dst_can_put_count)
428 		count = dst_can_put_count;
429 
430 	/* Limit according to source item count */
431 	if (count > list_from->getItem(from_i).count)
432 		count = list_from->getItem(from_i).count;
433 
434 	/* If no items will be moved, don't go further */
435 	if (count == 0) {
436 		if (caused_by_move_somewhere)
437 			// Set move count to zero, as no items have been moved
438 			move_count = 0;
439 
440 		// Undo client prediction. See 'clientApply'
441 		if (from_inv.type == InventoryLocation::PLAYER)
442 			list_from->setModified();
443 
444 		if (to_inv.type == InventoryLocation::PLAYER)
445 			list_to->setModified();
446 
447 		infostream<<"IMoveAction::apply(): move was completely disallowed:"
448 				<<" count="<<old_count
449 				<<" from inv=\""<<from_inv.dump()<<"\""
450 				<<" list=\""<<from_list<<"\""
451 				<<" i="<<from_i
452 				<<" to inv=\""<<to_inv.dump()<<"\""
453 				<<" list=\""<<to_list<<"\""
454 				<<" i="<<to_i
455 				<<std::endl;
456 
457 		return;
458 	}
459 
460 	src_item = list_from->getItem(from_i);
461 	src_item.count = count;
462 	ItemStack from_stack_was = list_from->getItem(from_i);
463 	ItemStack to_stack_was = list_to->getItem(to_i);
464 
465 	/*
466 		Perform actual move
467 
468 		If something is wrong (source item is empty, destination is the
469 		same as source), nothing happens
470 	*/
471 	bool did_swap = false;
472 	move_count = list_from->moveItem(from_i,
473 		list_to, to_i, count, allow_swap, &did_swap);
474 	if (caused_by_move_somewhere)
475 		count = old_count;
476 	assert(allow_swap == did_swap);
477 
478 	// If source is infinite, reset it's stack
479 	if (src_can_take_count == -1) {
480 		// For the caused_by_move_somewhere == true case we didn't force-put the item,
481 		// which guarantees there is no leftover, and code below would duplicate the
482 		// (not replaced) to_stack_was item.
483 		if (!caused_by_move_somewhere) {
484 			// If destination stack is of different type and there are leftover
485 			// items, attempt to put the leftover items to a different place in the
486 			// destination inventory.
487 			// The client-side GUI will try to guess if this happens.
488 			if (from_stack_was.name != to_stack_was.name) {
489 				for (u32 i = 0; i < list_to->getSize(); i++) {
490 					if (list_to->getItem(i).empty()) {
491 						list_to->changeItem(i, to_stack_was);
492 						break;
493 					}
494 				}
495 			}
496 		}
497 		if (move_count > 0 || did_swap) {
498 			list_from->deleteItem(from_i);
499 			list_from->addItem(from_i, from_stack_was);
500 		}
501 	}
502 	// If destination is infinite, reset it's stack and take count from source
503 	if (dst_can_put_count == -1) {
504 		list_to->deleteItem(to_i);
505 		list_to->addItem(to_i, to_stack_was);
506 		list_from->deleteItem(from_i);
507 		list_from->addItem(from_i, from_stack_was);
508 		list_from->takeItem(from_i, count);
509 	}
510 
511 	infostream << "IMoveAction::apply(): moved"
512 			<< " msom=" << move_somewhere
513 			<< " caused=" << caused_by_move_somewhere
514 			<< " count=" << count
515 			<< " from inv=\"" << from_inv.dump() << "\""
516 			<< " list=\"" << from_list << "\""
517 			<< " i=" << from_i
518 			<< " to inv=\"" << to_inv.dump() << "\""
519 			<< " list=\"" << to_list << "\""
520 			<< " i=" << to_i
521 			<< std::endl;
522 
523 	// If we are inside the move somewhere loop, we don't need to report
524 	// anything if nothing happened
525 	if (caused_by_move_somewhere && move_count == 0)
526 		return;
527 
528 	/*
529 		Record rollback information
530 	*/
531 	if (!ignore_rollback && gamedef->rollback()) {
532 		IRollbackManager *rollback = gamedef->rollback();
533 
534 		// If source is not infinite, record item take
535 		if (src_can_take_count != -1) {
536 			RollbackAction action;
537 			std::string loc;
538 			{
539 				std::ostringstream os(std::ios::binary);
540 				from_inv.serialize(os);
541 				loc = os.str();
542 			}
543 			action.setModifyInventoryStack(loc, from_list, from_i, false,
544 					src_item);
545 			rollback->reportAction(action);
546 		}
547 		// If destination is not infinite, record item put
548 		if (dst_can_put_count != -1) {
549 			RollbackAction action;
550 			std::string loc;
551 			{
552 				std::ostringstream os(std::ios::binary);
553 				to_inv.serialize(os);
554 				loc = os.str();
555 			}
556 			action.setModifyInventoryStack(loc, to_list, to_i, true,
557 					src_item);
558 			rollback->reportAction(action);
559 		}
560 	}
561 
562 	/*
563 		Report move to endpoints
564 	*/
565 
566 	// Source = destination => move
567 	if (from_inv == to_inv) {
568 		onMove(count, player);
569 		if (did_swap) {
570 			// Item is now placed in source list
571 			src_item = list_from->getItem(from_i);
572 			swapDirections();
573 			onMove(src_item.count, player);
574 			swapDirections();
575 		}
576 		mgr->setInventoryModified(from_inv);
577 	} else {
578 		int src_item_count = src_item.count;
579 		if (caused_by_move_somewhere)
580 			// When moving somewhere: temporarily use the actual movable stack
581 			// size to ensure correct callback execution.
582 			src_item.count = move_count;
583 		onPutAndOnTake(src_item, player);
584 		if (caused_by_move_somewhere)
585 			// Reset source item count
586 			src_item.count = src_item_count;
587 		if (did_swap) {
588 			// Item is now placed in source list
589 			src_item = list_from->getItem(from_i);
590 			swapDirections();
591 			onPutAndOnTake(src_item, player);
592 			swapDirections();
593 		}
594 		mgr->setInventoryModified(to_inv);
595 		mgr->setInventoryModified(from_inv);
596 	}
597 }
598 
clientApply(InventoryManager * mgr,IGameDef * gamedef)599 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
600 {
601 	// Optional InventoryAction operation that is run on the client
602 	// to make lag less apparent.
603 
604 	Inventory *inv_from = mgr->getInventory(from_inv);
605 	Inventory *inv_to = mgr->getInventory(to_inv);
606 	if (!inv_from || !inv_to)
607 		return;
608 
609 	InventoryLocation current_player;
610 	current_player.setCurrentPlayer();
611 	Inventory *inv_player = mgr->getInventory(current_player);
612 	if (inv_from != inv_player || inv_to != inv_player)
613 		return;
614 
615 	InventoryList *list_from = inv_from->getList(from_list);
616 	InventoryList *list_to = inv_to->getList(to_list);
617 	if (!list_from || !list_to)
618 		return;
619 
620 	if (!move_somewhere)
621 		list_from->moveItem(from_i, list_to, to_i, count);
622 	else
623 		list_from->moveItemSomewhere(from_i, list_to, count);
624 
625 	mgr->setInventoryModified(from_inv);
626 	if (inv_from != inv_to)
627 		mgr->setInventoryModified(to_inv);
628 }
629 
630 /*
631 	IDropAction
632 */
633 
IDropAction(std::istream & is)634 IDropAction::IDropAction(std::istream &is)
635 {
636 	std::string ts;
637 
638 	std::getline(is, ts, ' ');
639 	count = stoi(ts);
640 
641 	std::getline(is, ts, ' ');
642 	from_inv.deSerialize(ts);
643 
644 	std::getline(is, from_list, ' ');
645 
646 	std::getline(is, ts, ' ');
647 	from_i = stoi(ts);
648 }
649 
apply(InventoryManager * mgr,ServerActiveObject * player,IGameDef * gamedef)650 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
651 {
652 	Inventory *inv_from = mgr->getInventory(from_inv);
653 
654 	if (!inv_from) {
655 		infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
656 				<<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
657 		return;
658 	}
659 
660 	InventoryList *list_from = inv_from->getList(from_list);
661 
662 	/*
663 		If a list doesn't exist or the source item doesn't exist
664 	*/
665 	if (!list_from) {
666 		infostream<<"IDropAction::apply(): FAIL: source list not found: "
667 				<<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
668 		return;
669 	}
670 	if (list_from->getItem(from_i).empty()) {
671 		infostream<<"IDropAction::apply(): FAIL: source item not found: "
672 				<<"from_inv=\""<<from_inv.dump()<<"\""
673 				<<", from_list=\""<<from_list<<"\""
674 				<<" from_i="<<from_i<<std::endl;
675 		return;
676 	}
677 
678 	/*
679 		Do not handle rollback if inventory is player's
680 	*/
681 	bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
682 
683 	/*
684 		Collect information of endpoints
685 	*/
686 
687 	int take_count = list_from->getItem(from_i).count;
688 	if (count != 0 && count < take_count)
689 		take_count = count;
690 	int src_can_take_count = take_count;
691 
692 	ItemStack src_item = list_from->getItem(from_i);
693 	src_item.count = take_count;
694 
695 	// Run callbacks depending on source inventory
696 	switch (from_inv.type) {
697 	case InventoryLocation::DETACHED:
698 		src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
699 			*this, src_item, player);
700 		break;
701 	case InventoryLocation::NODEMETA:
702 		src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
703 			*this, src_item, player);
704 		break;
705 	case InventoryLocation::PLAYER:
706 		src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake(
707 			*this, src_item, player);
708 		break;
709 	default:
710 		break;
711 	}
712 
713 	if (src_can_take_count != -1 && src_can_take_count < take_count)
714 		take_count = src_can_take_count;
715 
716 	// Update item due executed callbacks
717 	src_item = list_from->getItem(from_i);
718 
719 	// Drop the item
720 	ItemStack item1 = list_from->getItem(from_i);
721 	item1.count = take_count;
722 	if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
723 				player->getBasePosition())) {
724 		int actually_dropped_count = take_count - item1.count;
725 
726 		if (actually_dropped_count == 0) {
727 			infostream<<"Actually dropped no items"<<std::endl;
728 
729 			// Revert client prediction. See 'clientApply'
730 			if (from_inv.type == InventoryLocation::PLAYER)
731 				list_from->setModified();
732 			return;
733 		}
734 
735 		// If source isn't infinite
736 		if (src_can_take_count != -1) {
737 			// Take item from source list
738 			ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
739 
740 			if (item2.count != actually_dropped_count)
741 				errorstream<<"Could not take dropped count of items"<<std::endl;
742 		}
743 
744 		src_item.count = actually_dropped_count;
745 		mgr->setInventoryModified(from_inv);
746 	}
747 
748 	infostream<<"IDropAction::apply(): dropped "
749 			<<" from inv=\""<<from_inv.dump()<<"\""
750 			<<" list=\""<<from_list<<"\""
751 			<<" i="<<from_i
752 			<<std::endl;
753 
754 
755 	/*
756 		Report drop to endpoints
757 	*/
758 
759 	switch (from_inv.type) {
760 	case InventoryLocation::DETACHED:
761 		PLAYER_TO_SA(player)->detached_inventory_OnTake(
762 			*this, src_item, player);
763 		break;
764 	case InventoryLocation::NODEMETA:
765 		PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
766 			*this, src_item, player);
767 		break;
768 	case InventoryLocation::PLAYER:
769 		PLAYER_TO_SA(player)->player_inventory_OnTake(
770 			*this, src_item, player);
771 		break;
772 	default:
773 		break;
774 	}
775 
776 	/*
777 		Record rollback information
778 	*/
779 	if (!ignore_src_rollback && gamedef->rollback()) {
780 		IRollbackManager *rollback = gamedef->rollback();
781 
782 		// If source is not infinite, record item take
783 		if (src_can_take_count != -1) {
784 			RollbackAction action;
785 			std::string loc;
786 			{
787 				std::ostringstream os(std::ios::binary);
788 				from_inv.serialize(os);
789 				loc = os.str();
790 			}
791 			action.setModifyInventoryStack(loc, from_list, from_i,
792 					false, src_item);
793 			rollback->reportAction(action);
794 		}
795 	}
796 }
797 
clientApply(InventoryManager * mgr,IGameDef * gamedef)798 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
799 {
800 	// Optional InventoryAction operation that is run on the client
801 	// to make lag less apparent.
802 
803 	Inventory *inv_from = mgr->getInventory(from_inv);
804 	if (!inv_from)
805 		return;
806 
807 	InventoryLocation current_player;
808 	current_player.setCurrentPlayer();
809 	Inventory *inv_player = mgr->getInventory(current_player);
810 	if (inv_from != inv_player)
811 		return;
812 
813 	InventoryList *list_from = inv_from->getList(from_list);
814 	if (!list_from)
815 		return;
816 
817 	if (count == 0)
818 		list_from->changeItem(from_i, ItemStack());
819 	else
820 		list_from->takeItem(from_i, count);
821 
822 	mgr->setInventoryModified(from_inv);
823 }
824 
825 /*
826 	ICraftAction
827 */
828 
ICraftAction(std::istream & is)829 ICraftAction::ICraftAction(std::istream &is)
830 {
831 	std::string ts;
832 
833 	std::getline(is, ts, ' ');
834 	count = stoi(ts);
835 
836 	std::getline(is, ts, ' ');
837 	craft_inv.deSerialize(ts);
838 }
839 
apply(InventoryManager * mgr,ServerActiveObject * player,IGameDef * gamedef)840 void ICraftAction::apply(InventoryManager *mgr,
841 	ServerActiveObject *player, IGameDef *gamedef)
842 {
843 	Inventory *inv_craft = mgr->getInventory(craft_inv);
844 
845 	if (!inv_craft) {
846 		infostream << "ICraftAction::apply(): FAIL: inventory not found: "
847 				<< "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
848 		return;
849 	}
850 
851 	InventoryList *list_craft = inv_craft->getList("craft");
852 	InventoryList *list_craftresult = inv_craft->getList("craftresult");
853 	InventoryList *list_main = inv_craft->getList("main");
854 
855 	/*
856 		If a list doesn't exist or the source item doesn't exist
857 	*/
858 	if (!list_craft) {
859 		infostream << "ICraftAction::apply(): FAIL: craft list not found: "
860 				<< "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
861 		return;
862 	}
863 	if (!list_craftresult) {
864 		infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
865 				<< "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
866 		return;
867 	}
868 	if (list_craftresult->getSize() < 1) {
869 		infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
870 				<< "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
871 		return;
872 	}
873 
874 	ItemStack crafted;
875 	ItemStack craftresultitem;
876 	int count_remaining = count;
877 	std::vector<ItemStack> output_replacements;
878 	getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
879 	PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
880 	bool found = !crafted.empty();
881 
882 	while (found && list_craftresult->itemFits(0, crafted)) {
883 		InventoryList saved_craft_list = *list_craft;
884 
885 		std::vector<ItemStack> temp;
886 		// Decrement input and add crafting output
887 		getCraftingResult(inv_craft, crafted, temp, true, gamedef);
888 		PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
889 		list_craftresult->addItem(0, crafted);
890 		mgr->setInventoryModified(craft_inv);
891 
892 		// Add the new replacements to the list
893 		IItemDefManager *itemdef = gamedef->getItemDefManager();
894 		for (auto &itemstack : temp) {
895 			for (auto &output_replacement : output_replacements) {
896 				if (itemstack.name == output_replacement.name) {
897 					itemstack = output_replacement.addItem(itemstack, itemdef);
898 					if (itemstack.empty())
899 						continue;
900 				}
901 			}
902 			output_replacements.push_back(itemstack);
903 		}
904 
905 		actionstream << player->getDescription()
906 				<< " crafts "
907 				<< crafted.getItemString()
908 				<< std::endl;
909 
910 		// Decrement counter
911 		if (count_remaining == 1)
912 			break;
913 
914 		if (count_remaining > 1)
915 			count_remaining--;
916 
917 		// Get next crafting result
918 		getCraftingResult(inv_craft, crafted, temp, false, gamedef);
919 		PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
920 		found = !crafted.empty();
921 	}
922 
923 	// Put the replacements in the inventory or drop them on the floor, if
924 	// the inventory is full
925 	for (auto &output_replacement : output_replacements) {
926 		if (list_main)
927 			output_replacement = list_main->addItem(output_replacement);
928 		if (output_replacement.empty())
929 			continue;
930 		u16 count = output_replacement.count;
931 		do {
932 			PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player,
933 				player->getBasePosition());
934 			if (count >= output_replacement.count) {
935 				errorstream << "Couldn't drop replacement stack " <<
936 					output_replacement.getItemString() << " because drop loop didn't "
937 					"decrease count." << std::endl;
938 
939 				break;
940 			}
941 		} while (!output_replacement.empty());
942 	}
943 
944 	infostream<<"ICraftAction::apply(): crafted "
945 			<<" craft_inv=\""<<craft_inv.dump()<<"\""
946 			<<std::endl;
947 }
948 
clientApply(InventoryManager * mgr,IGameDef * gamedef)949 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
950 {
951 	// Optional InventoryAction operation that is run on the client
952 	// to make lag less apparent.
953 }
954 
955 
956 // Crafting helper
getCraftingResult(Inventory * inv,ItemStack & result,std::vector<ItemStack> & output_replacements,bool decrementInput,IGameDef * gamedef)957 bool getCraftingResult(Inventory *inv, ItemStack &result,
958 		std::vector<ItemStack> &output_replacements,
959 		bool decrementInput, IGameDef *gamedef)
960 {
961 	result.clear();
962 
963 	// Get the InventoryList in which we will operate
964 	InventoryList *clist = inv->getList("craft");
965 	if (!clist)
966 		return false;
967 
968 	// Mangle crafting grid to an another format
969 	CraftInput ci;
970 	ci.method = CRAFT_METHOD_NORMAL;
971 	ci.width = clist->getWidth() ? clist->getWidth() : 3;
972 	for (u16 i=0; i < clist->getSize(); i++)
973 		ci.items.push_back(clist->getItem(i));
974 
975 	// Find out what is crafted and add it to result item slot
976 	CraftOutput co;
977 	bool found = gamedef->getCraftDefManager()->getCraftResult(
978 			ci, co, output_replacements, decrementInput, gamedef);
979 	if (found)
980 		result.deSerialize(co.item, gamedef->getItemDefManager());
981 
982 	if (found && decrementInput) {
983 		// CraftInput has been changed, apply changes in clist
984 		for (u16 i=0; i < clist->getSize(); i++) {
985 			clist->changeItem(i, ci.items[i]);
986 		}
987 	}
988 
989 	return found;
990 }
991 
992