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