1a7f56f315534d1791e691997b1eabb20ac95aff
[oweals/minetest.git] / src / inventorymanager.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010-2011 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->takeItem(from_i, count);
355         }
356
357         infostream<<"IMoveAction::apply(): moved"
358                         <<" count="<<count
359                         <<" from inv=\""<<from_inv.dump()<<"\""
360                         <<" list=\""<<from_list<<"\""
361                         <<" i="<<from_i
362                         <<" to inv=\""<<to_inv.dump()<<"\""
363                         <<" list=\""<<to_list<<"\""
364                         <<" i="<<to_i
365                         <<std::endl;
366
367         /*
368                 Record rollback information
369         */
370         if(!ignore_rollback && gamedef->rollback())
371         {
372                 IRollbackReportSink *rollback = gamedef->rollback();
373
374                 // If source is not infinite, record item take
375                 if(src_can_take_count != -1){
376                         RollbackAction action;
377                         std::string loc;
378                         {
379                                 std::ostringstream os(std::ios::binary);
380                                 from_inv.serialize(os);
381                                 loc = os.str();
382                         }
383                         action.setModifyInventoryStack(loc, from_list, from_i, false,
384                                         src_item.getItemString());
385                         rollback->reportAction(action);
386                 }
387                 // If destination is not infinite, record item put
388                 if(dst_can_put_count != -1){
389                         RollbackAction action;
390                         std::string loc;
391                         {
392                                 std::ostringstream os(std::ios::binary);
393                                 to_inv.serialize(os);
394                                 loc = os.str();
395                         }
396                         action.setModifyInventoryStack(loc, to_list, to_i, true,
397                                         src_item.getItemString());
398                         rollback->reportAction(action);
399                 }
400         }
401
402         /*
403                 Report move to endpoints
404         */
405         
406         /* Detached inventories */
407
408         // Both endpoints are same detached
409         if(from_inv.type == InventoryLocation::DETACHED &&
410                         to_inv.type == InventoryLocation::DETACHED &&
411                         from_inv.name == to_inv.name)
412         {
413                 lua_State *L = player->getEnv()->getLua();
414                 scriptapi_detached_inventory_on_move(
415                                 L, from_inv.name, from_list, from_i,
416                                 to_list, to_i, count, player);
417         }
418         else
419         {
420                 // Destination is detached
421                 if(to_inv.type == InventoryLocation::DETACHED)
422                 {
423                         lua_State *L = player->getEnv()->getLua();
424                         scriptapi_detached_inventory_on_put(
425                                         L, to_inv.name, to_list, to_i, src_item, player);
426                 }
427                 // Source is detached
428                 if(from_inv.type == InventoryLocation::DETACHED)
429                 {
430                         lua_State *L = player->getEnv()->getLua();
431                         scriptapi_detached_inventory_on_take(
432                                         L, from_inv.name, from_list, from_i, src_item, player);
433                 }
434         }
435
436         /* Node metadata inventories */
437
438         // Both endpoints are same nodemeta
439         if(from_inv.type == InventoryLocation::NODEMETA &&
440                         to_inv.type == InventoryLocation::NODEMETA &&
441                         from_inv.p == to_inv.p)
442         {
443                 lua_State *L = player->getEnv()->getLua();
444                 scriptapi_nodemeta_inventory_on_move(
445                                 L, from_inv.p, from_list, from_i,
446                                 to_list, to_i, count, player);
447         }
448         else{
449                 // Destination is nodemeta
450                 if(to_inv.type == InventoryLocation::NODEMETA)
451                 {
452                         lua_State *L = player->getEnv()->getLua();
453                         scriptapi_nodemeta_inventory_on_put(
454                                         L, to_inv.p, to_list, to_i, src_item, player);
455                 }
456                 // Source is nodemeta
457                 else if(from_inv.type == InventoryLocation::NODEMETA)
458                 {
459                         lua_State *L = player->getEnv()->getLua();
460                         scriptapi_nodemeta_inventory_on_take(
461                                         L, from_inv.p, from_list, from_i, src_item, player);
462                 }
463         }
464         
465         mgr->setInventoryModified(from_inv);
466         if(inv_from != inv_to)
467                 mgr->setInventoryModified(to_inv);
468 }
469
470 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
471 {
472         // Optional InventoryAction operation that is run on the client
473         // to make lag less apparent.
474
475         Inventory *inv_from = mgr->getInventory(from_inv);
476         Inventory *inv_to = mgr->getInventory(to_inv);
477         if(!inv_from || !inv_to)
478                 return;
479
480         InventoryLocation current_player;
481         current_player.setCurrentPlayer();
482         Inventory *inv_player = mgr->getInventory(current_player);
483         if(inv_from != inv_player || inv_to != inv_player)
484                 return;
485
486         InventoryList *list_from = inv_from->getList(from_list);
487         InventoryList *list_to = inv_to->getList(to_list);
488         if(!list_from || !list_to)
489                 return;
490
491         list_from->moveItem(from_i, list_to, to_i, count);
492
493         mgr->setInventoryModified(from_inv);
494         if(inv_from != inv_to)
495                 mgr->setInventoryModified(to_inv);
496 }
497
498 /*
499         IDropAction
500 */
501
502 IDropAction::IDropAction(std::istream &is)
503 {
504         std::string ts;
505
506         std::getline(is, ts, ' ');
507         count = stoi(ts);
508
509         std::getline(is, ts, ' ');
510         from_inv.deSerialize(ts);
511
512         std::getline(is, from_list, ' ');
513
514         std::getline(is, ts, ' ');
515         from_i = stoi(ts);
516 }
517
518 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
519 {
520         Inventory *inv_from = mgr->getInventory(from_inv);
521         
522         if(!inv_from){
523                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
524                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
525                 return;
526         }
527
528         InventoryList *list_from = inv_from->getList(from_list);
529
530         /*
531                 If a list doesn't exist or the source item doesn't exist
532         */
533         if(!list_from){
534                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
535                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
536                 return;
537         }
538         if(list_from->getItem(from_i).empty())
539         {
540                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
541                                 <<"from_inv=\""<<from_inv.dump()<<"\""
542                                 <<", from_list=\""<<from_list<<"\""
543                                 <<" from_i="<<from_i<<std::endl;
544                 return;
545         }
546
547         /*
548                 Do not handle rollback if inventory is player's
549         */
550         bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
551
552         /*
553                 Collect information of endpoints
554         */
555
556         int take_count = list_from->getItem(from_i).count;
557         if(count != 0 && count < take_count)
558                 take_count = count;
559         int src_can_take_count = take_count;
560
561         // Source is detached
562         if(from_inv.type == InventoryLocation::DETACHED)
563         {
564                 lua_State *L = player->getEnv()->getLua();
565                 ItemStack src_item = list_from->getItem(from_i);
566                 src_item.count = take_count;
567                 src_can_take_count = scriptapi_detached_inventory_allow_take(
568                                 L, from_inv.name, from_list, from_i, src_item, player);
569         }
570
571         // Source is nodemeta
572         if(from_inv.type == InventoryLocation::NODEMETA)
573         {
574                 lua_State *L = player->getEnv()->getLua();
575                 ItemStack src_item = list_from->getItem(from_i);
576                 src_item.count = take_count;
577                 src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
578                                 L, from_inv.p, from_list, from_i, src_item, player);
579         }
580
581         if(src_can_take_count != -1 && src_can_take_count < take_count)
582                 take_count = src_can_take_count;
583         
584         int actually_dropped_count = 0;
585
586         ItemStack src_item = list_from->getItem(from_i);
587
588         // Drop the item
589         ItemStack item1 = list_from->getItem(from_i);
590         item1.count = take_count;
591         if(scriptapi_item_on_drop(player->getEnv()->getLua(), item1, player,
592                                 player->getBasePosition() + v3f(0,1,0)))
593         {
594                 actually_dropped_count = take_count - item1.count;
595
596                 if(actually_dropped_count == 0){
597                         infostream<<"Actually dropped no items"<<std::endl;
598                         return;
599                 }
600                 
601                 // If source isn't infinite
602                 if(src_can_take_count != -1){
603                         // Take item from source list
604                         ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
605
606                         if(item2.count != actually_dropped_count)
607                                 errorstream<<"Could not take dropped count of items"<<std::endl;
608
609                         mgr->setInventoryModified(from_inv);
610                 }
611         }
612
613         infostream<<"IDropAction::apply(): dropped "
614                         <<" from inv=\""<<from_inv.dump()<<"\""
615                         <<" list=\""<<from_list<<"\""
616                         <<" i="<<from_i
617                         <<std::endl;
618         
619         src_item.count = actually_dropped_count;
620
621         /*
622                 Report drop to endpoints
623         */
624         
625         // Source is detached
626         if(from_inv.type == InventoryLocation::DETACHED)
627         {
628                 lua_State *L = player->getEnv()->getLua();
629                 scriptapi_detached_inventory_on_take(
630                                 L, from_inv.name, from_list, from_i, src_item, player);
631         }
632
633         // Source is nodemeta
634         if(from_inv.type == InventoryLocation::NODEMETA)
635         {
636                 lua_State *L = player->getEnv()->getLua();
637                 scriptapi_nodemeta_inventory_on_take(
638                                 L, from_inv.p, from_list, from_i, src_item, player);
639         }
640
641         /*
642                 Record rollback information
643         */
644         if(!ignore_src_rollback && gamedef->rollback())
645         {
646                 IRollbackReportSink *rollback = gamedef->rollback();
647
648                 // If source is not infinite, record item take
649                 if(src_can_take_count != -1){
650                         RollbackAction action;
651                         std::string loc;
652                         {
653                                 std::ostringstream os(std::ios::binary);
654                                 from_inv.serialize(os);
655                                 loc = os.str();
656                         }
657                         action.setModifyInventoryStack(loc, from_list, from_i,
658                                         false, src_item.getItemString());
659                         rollback->reportAction(action);
660                 }
661         }
662 }
663
664 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
665 {
666         // Optional InventoryAction operation that is run on the client
667         // to make lag less apparent.
668
669         Inventory *inv_from = mgr->getInventory(from_inv);
670         if(!inv_from)
671                 return;
672
673         InventoryLocation current_player;
674         current_player.setCurrentPlayer();
675         Inventory *inv_player = mgr->getInventory(current_player);
676         if(inv_from != inv_player)
677                 return;
678
679         InventoryList *list_from = inv_from->getList(from_list);
680         if(!list_from)
681                 return;
682
683         if(count == 0)
684                 list_from->changeItem(from_i, ItemStack());
685         else
686                 list_from->takeItem(from_i, count);
687
688         mgr->setInventoryModified(from_inv);
689 }
690
691 /*
692         ICraftAction
693 */
694
695 ICraftAction::ICraftAction(std::istream &is)
696 {
697         std::string ts;
698
699         std::getline(is, ts, ' ');
700         count = stoi(ts);
701
702         std::getline(is, ts, ' ');
703         craft_inv.deSerialize(ts);
704 }
705
706 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
707 {
708         Inventory *inv_craft = mgr->getInventory(craft_inv);
709         
710         if(!inv_craft){
711                 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
712                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
713                 return;
714         }
715
716         InventoryList *list_craft = inv_craft->getList("craft");
717         InventoryList *list_craftresult = inv_craft->getList("craftresult");
718
719         /*
720                 If a list doesn't exist or the source item doesn't exist
721         */
722         if(!list_craft){
723                 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
724                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
725                 return;
726         }
727         if(!list_craftresult){
728                 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
729                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
730                 return;
731         }
732         if(list_craftresult->getSize() < 1){
733                 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
734                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
735                 return;
736         }
737
738         ItemStack crafted;
739         int count_remaining = count;
740         bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
741
742         while(found && list_craftresult->itemFits(0, crafted))
743         {
744                 // Decrement input and add crafting output
745                 getCraftingResult(inv_craft, crafted, true, gamedef);
746                 list_craftresult->addItem(0, crafted);
747                 mgr->setInventoryModified(craft_inv);
748
749                 actionstream<<player->getDescription()
750                                 <<" crafts "
751                                 <<crafted.getItemString()
752                                 <<std::endl;
753
754                 // Decrement counter
755                 if(count_remaining == 1)
756                         break;
757                 else if(count_remaining > 1)
758                         count_remaining--;
759
760                 // Get next crafting result
761                 found = getCraftingResult(inv_craft, crafted, false, gamedef);
762         }
763
764         infostream<<"ICraftAction::apply(): crafted "
765                         <<" craft_inv=\""<<craft_inv.dump()<<"\""
766                         <<std::endl;
767 }
768
769 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
770 {
771         // Optional InventoryAction operation that is run on the client
772         // to make lag less apparent.
773 }
774
775
776 // Crafting helper
777 bool getCraftingResult(Inventory *inv, ItemStack& result,
778                 bool decrementInput, IGameDef *gamedef)
779 {
780         DSTACK(__FUNCTION_NAME);
781         
782         result.clear();
783
784         // Get the InventoryList in which we will operate
785         InventoryList *clist = inv->getList("craft");
786         if(!clist)
787                 return false;
788
789         // Mangle crafting grid to an another format
790         CraftInput ci;
791         ci.method = CRAFT_METHOD_NORMAL;
792         ci.width = clist->getWidth() ? clist->getWidth() : 3;
793         for(u16 i=0; i<clist->getSize(); i++)
794                 ci.items.push_back(clist->getItem(i));
795
796         // Find out what is crafted and add it to result item slot
797         CraftOutput co;
798         bool found = gamedef->getCraftDefManager()->getCraftResult(
799                         ci, co, decrementInput, gamedef);
800         if(found)
801                 result.deSerialize(co.item, gamedef->getItemDefManager());
802
803         if(found && decrementInput)
804         {
805                 // CraftInput has been changed, apply changes in clist
806                 for(u16 i=0; i<clist->getSize(); i++)
807                 {
808                         clist->changeItem(i, ci.items[i]);
809                 }
810         }
811
812         return found;
813 }
814