]> git.lizzy.rs Git - dragonfireclient.git/blob - src/inventorymanager.cpp
Merge pull request #35 from arydevy/patch-1
[dragonfireclient.git] / src / inventorymanager.cpp
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
38 std::string InventoryLocation::dump() const
39 {
40         std::ostringstream os(std::ios::binary);
41         serialize(os);
42         return os.str();
43 }
44
45 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
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
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
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
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
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
164 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
186 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
199 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
214 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
229 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
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
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
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
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
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
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
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
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
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