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