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