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