]> git.lizzy.rs Git - dragonfireclient.git/blob - src/inventorymanager.cpp
Omnicleanup: header cleanup, add ModApiUtil shared between game and mainmenu
[dragonfireclient.git] / src / inventorymanager.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "inventorymanager.h"
21 #include "log.h"
22 #include "environment.h"
23 #include "scripting_game.h"
24 #include "serverobject.h"
25 #include "main.h"  // for g_settings
26 #include "settings.h"
27 #include "craftdef.h"
28 #include "rollback_interface.h"
29
30 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
31
32 #define PLAYER_TO_SA(p)   p->getEnv()->getScriptIface()
33
34 /*
35         InventoryLocation
36 */
37
38 std::string InventoryLocation::dump() const
39 {
40         std::ostringstream os(std::ios::binary);
41         serialize(os);
42         return os.str();
43 }
44
45 void InventoryLocation::serialize(std::ostream &os) const
46 {
47         switch(type){
48         case InventoryLocation::UNDEFINED:
49                 os<<"undefined";
50                 break;
51         case InventoryLocation::CURRENT_PLAYER:
52                 os<<"current_player";
53                 break;
54         case InventoryLocation::PLAYER:
55                 os<<"player:"<<name;
56                 break;
57         case InventoryLocation::NODEMETA:
58                 os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
59                 break;
60         case InventoryLocation::DETACHED:
61                 os<<"detached:"<<name;
62                 break;
63         default:
64                 assert(0);
65         }
66 }
67
68 void InventoryLocation::deSerialize(std::istream &is)
69 {
70         std::string tname;
71         std::getline(is, tname, ':');
72         if(tname == "undefined")
73         {
74                 type = InventoryLocation::UNDEFINED;
75         }
76         else if(tname == "current_player")
77         {
78                 type = InventoryLocation::CURRENT_PLAYER;
79         }
80         else if(tname == "player")
81         {
82                 type = InventoryLocation::PLAYER;
83                 std::getline(is, name, '\n');
84         }
85         else if(tname == "nodemeta")
86         {
87                 type = InventoryLocation::NODEMETA;
88                 std::string pos;
89                 std::getline(is, pos, '\n');
90                 Strfnd fn(pos);
91                 p.X = stoi(fn.next(","));
92                 p.Y = stoi(fn.next(","));
93                 p.Z = stoi(fn.next(","));
94         }
95         else if(tname == "detached")
96         {
97                 type = InventoryLocation::DETACHED;
98                 std::getline(is, name, '\n');
99         }
100         else
101         {
102                 infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
103                 throw SerializationError("Unknown InventoryLocation type");
104         }
105 }
106
107 void InventoryLocation::deSerialize(std::string s)
108 {
109         std::istringstream is(s, std::ios::binary);
110         deSerialize(is);
111 }
112
113 /*
114         InventoryAction
115 */
116
117 InventoryAction * InventoryAction::deSerialize(std::istream &is)
118 {
119         std::string type;
120         std::getline(is, type, ' ');
121
122         InventoryAction *a = NULL;
123
124         if(type == "Move")
125         {
126                 a = new IMoveAction(is);
127         }
128         else if(type == "Drop")
129         {
130                 a = new IDropAction(is);
131         }
132         else if(type == "Craft")
133         {
134                 a = new ICraftAction(is);
135         }
136
137         return a;
138 }
139
140 /*
141         IMoveAction
142 */
143
144 IMoveAction::IMoveAction(std::istream &is)
145 {
146         std::string ts;
147
148         std::getline(is, ts, ' ');
149         count = stoi(ts);
150
151         std::getline(is, ts, ' ');
152         from_inv.deSerialize(ts);
153
154         std::getline(is, from_list, ' ');
155
156         std::getline(is, ts, ' ');
157         from_i = stoi(ts);
158
159         std::getline(is, ts, ' ');
160         to_inv.deSerialize(ts);
161
162         std::getline(is, to_list, ' ');
163
164         std::getline(is, ts, ' ');
165         to_i = stoi(ts);
166 }
167
168 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
169 {
170         Inventory *inv_from = mgr->getInventory(from_inv);
171         Inventory *inv_to = mgr->getInventory(to_inv);
172         
173         if(!inv_from){
174                 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
175                                 <<"from_inv=\""<<from_inv.dump()<<"\""
176                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
177                 return;
178         }
179         if(!inv_to){
180                 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
181                                 <<"from_inv=\""<<from_inv.dump()<<"\""
182                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
183                 return;
184         }
185
186         InventoryList *list_from = inv_from->getList(from_list);
187         InventoryList *list_to = inv_to->getList(to_list);
188
189         /*
190                 If a list doesn't exist or the source item doesn't exist
191         */
192         if(!list_from){
193                 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
194                                 <<"from_inv=\""<<from_inv.dump()<<"\""
195                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
196                 return;
197         }
198         if(!list_to){
199                 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
200                                 <<"to_inv=\""<<to_inv.dump()<<"\""
201                                 <<", to_list=\""<<to_list<<"\""<<std::endl;
202                 return;
203         }
204
205         /*
206                 Do not handle rollback if both inventories are that of the same player
207         */
208         bool ignore_rollback = (
209                 from_inv.type == InventoryLocation::PLAYER &&
210                 to_inv.type == InventoryLocation::PLAYER &&
211                 from_inv.name == to_inv.name);
212
213         /*
214                 Collect information of endpoints
215         */
216
217         int try_take_count = count;
218         if(try_take_count == 0)
219                 try_take_count = list_from->getItem(from_i).count;
220
221         int src_can_take_count = 0xffff;
222         int dst_can_put_count = 0xffff;
223         
224         /* Query detached inventories */
225
226         // Move occurs in the same detached inventory
227         if(from_inv.type == InventoryLocation::DETACHED &&
228                         to_inv.type == InventoryLocation::DETACHED &&
229                         from_inv.name == to_inv.name)
230         {
231                 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove(
232                                 from_inv.name, from_list, from_i,
233                                 to_list, to_i, try_take_count, player);
234                 dst_can_put_count = src_can_take_count;
235         }
236         else
237         {
238                 // Destination is detached
239                 if(to_inv.type == InventoryLocation::DETACHED)
240                 {
241                         ItemStack src_item = list_from->getItem(from_i);
242                         src_item.count = try_take_count;
243                         dst_can_put_count = PLAYER_TO_SA(player)->detached_inventory_AllowPut(
244                                         to_inv.name, to_list, to_i, src_item, player);
245                 }
246                 // Source is detached
247                 if(from_inv.type == InventoryLocation::DETACHED)
248                 {
249                         ItemStack src_item = list_from->getItem(from_i);
250                         src_item.count = try_take_count;
251                         src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
252                                         from_inv.name, from_list, from_i, src_item, player);
253                 }
254         }
255
256         /* Query node metadata inventories */
257
258         // Both endpoints are nodemeta
259         // Move occurs in the same nodemeta inventory
260         if(from_inv.type == InventoryLocation::NODEMETA &&
261                         to_inv.type == InventoryLocation::NODEMETA &&
262                         from_inv.p == to_inv.p)
263         {
264                 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove(
265                                 from_inv.p, from_list, from_i,
266                                 to_list, to_i, try_take_count, player);
267                 dst_can_put_count = src_can_take_count;
268         }
269         else
270         {
271                 // Destination is nodemeta
272                 if(to_inv.type == InventoryLocation::NODEMETA)
273                 {
274                         ItemStack src_item = list_from->getItem(from_i);
275                         src_item.count = try_take_count;
276                         dst_can_put_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut(
277                                         to_inv.p, to_list, to_i, src_item, player);
278                 }
279                 // Source is nodemeta
280                 if(from_inv.type == InventoryLocation::NODEMETA)
281                 {
282                         ItemStack src_item = list_from->getItem(from_i);
283                         src_item.count = try_take_count;
284                         src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
285                                         from_inv.p, from_list, from_i, src_item, player);
286                 }
287         }
288
289         int old_count = count;
290         
291         /* Modify count according to collected data */
292         count = try_take_count;
293         if(src_can_take_count != -1 && count > src_can_take_count)
294                 count = src_can_take_count;
295         if(dst_can_put_count != -1 && count > dst_can_put_count)
296                 count = dst_can_put_count;
297         /* Limit according to source item count */
298         if(count > list_from->getItem(from_i).count)
299                 count = list_from->getItem(from_i).count;
300         
301         /* If no items will be moved, don't go further */
302         if(count == 0)
303         {
304                 infostream<<"IMoveAction::apply(): move was completely disallowed:"
305                                 <<" count="<<old_count
306                                 <<" from inv=\""<<from_inv.dump()<<"\""
307                                 <<" list=\""<<from_list<<"\""
308                                 <<" i="<<from_i
309                                 <<" to inv=\""<<to_inv.dump()<<"\""
310                                 <<" list=\""<<to_list<<"\""
311                                 <<" i="<<to_i
312                                 <<std::endl;
313                 return;
314         }
315
316         ItemStack src_item = list_from->getItem(from_i);
317         src_item.count = count;
318         ItemStack from_stack_was = list_from->getItem(from_i);
319         ItemStack to_stack_was = list_to->getItem(to_i);
320
321         /*
322                 Perform actual move
323
324                 If something is wrong (source item is empty, destination is the
325                 same as source), nothing happens
326         */
327         list_from->moveItem(from_i, list_to, to_i, count);
328
329         // If source is infinite, reset it's stack
330         if(src_can_take_count == -1){
331                 // If destination stack is of different type and there are leftover
332                 // items, attempt to put the leftover items to a different place in the
333                 // destination inventory.
334                 // The client-side GUI will try to guess if this happens.
335                 if(from_stack_was.name != to_stack_was.name){
336                         for(u32 i=0; i<list_to->getSize(); i++){
337                                 if(list_to->getItem(i).empty()){
338                                         list_to->changeItem(i, to_stack_was);
339                                         break;
340                                 }
341                         }
342                 }
343                 list_from->deleteItem(from_i);
344                 list_from->addItem(from_i, from_stack_was);
345         }
346         // If destination is infinite, reset it's stack and take count from source
347         if(dst_can_put_count == -1){
348                 list_to->deleteItem(to_i);
349                 list_to->addItem(to_i, to_stack_was);
350                 list_from->deleteItem(from_i);
351                 list_from->addItem(from_i, from_stack_was);
352                 list_from->takeItem(from_i, count);
353         }
354
355         infostream<<"IMoveAction::apply(): moved"
356                         <<" count="<<count
357                         <<" from inv=\""<<from_inv.dump()<<"\""
358                         <<" list=\""<<from_list<<"\""
359                         <<" i="<<from_i
360                         <<" to inv=\""<<to_inv.dump()<<"\""
361                         <<" list=\""<<to_list<<"\""
362                         <<" i="<<to_i
363                         <<std::endl;
364
365         /*
366                 Record rollback information
367         */
368         if(!ignore_rollback && gamedef->rollback())
369         {
370                 IRollbackReportSink *rollback = gamedef->rollback();
371
372                 // If source is not infinite, record item take
373                 if(src_can_take_count != -1){
374                         RollbackAction action;
375                         std::string loc;
376                         {
377                                 std::ostringstream os(std::ios::binary);
378                                 from_inv.serialize(os);
379                                 loc = os.str();
380                         }
381                         action.setModifyInventoryStack(loc, from_list, from_i, false,
382                                         src_item.getItemString());
383                         rollback->reportAction(action);
384                 }
385                 // If destination is not infinite, record item put
386                 if(dst_can_put_count != -1){
387                         RollbackAction action;
388                         std::string loc;
389                         {
390                                 std::ostringstream os(std::ios::binary);
391                                 to_inv.serialize(os);
392                                 loc = os.str();
393                         }
394                         action.setModifyInventoryStack(loc, to_list, to_i, true,
395                                         src_item.getItemString());
396                         rollback->reportAction(action);
397                 }
398         }
399
400         /*
401                 Report move to endpoints
402         */
403         
404         /* Detached inventories */
405
406         // Both endpoints are same detached
407         if(from_inv.type == InventoryLocation::DETACHED &&
408                         to_inv.type == InventoryLocation::DETACHED &&
409                         from_inv.name == to_inv.name)
410         {
411                 PLAYER_TO_SA(player)->detached_inventory_OnMove(
412                                 from_inv.name, from_list, from_i,
413                                 to_list, to_i, count, player);
414         }
415         else
416         {
417                 // Destination is detached
418                 if(to_inv.type == InventoryLocation::DETACHED)
419                 {
420                         PLAYER_TO_SA(player)->detached_inventory_OnPut(
421                                         to_inv.name, to_list, to_i, src_item, player);
422                 }
423                 // Source is detached
424                 if(from_inv.type == InventoryLocation::DETACHED)
425                 {
426                         PLAYER_TO_SA(player)->detached_inventory_OnTake(
427                                         from_inv.name, from_list, from_i, src_item, player);
428                 }
429         }
430
431         /* Node metadata inventories */
432
433         // Both endpoints are same nodemeta
434         if(from_inv.type == InventoryLocation::NODEMETA &&
435                         to_inv.type == InventoryLocation::NODEMETA &&
436                         from_inv.p == to_inv.p)
437         {
438                 PLAYER_TO_SA(player)->nodemeta_inventory_OnMove(
439                                 from_inv.p, from_list, from_i,
440                                 to_list, to_i, count, player);
441         }
442         else{
443                 // Destination is nodemeta
444                 if(to_inv.type == InventoryLocation::NODEMETA)
445                 {
446                         PLAYER_TO_SA(player)->nodemeta_inventory_OnPut(
447                                         to_inv.p, to_list, to_i, src_item, player);
448                 }
449                 // Source is nodemeta
450                 else if(from_inv.type == InventoryLocation::NODEMETA)
451                 {
452                         PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
453                                         from_inv.p, from_list, from_i, src_item, player);
454                 }
455         }
456         
457         mgr->setInventoryModified(from_inv);
458         if(inv_from != inv_to)
459                 mgr->setInventoryModified(to_inv);
460 }
461
462 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
463 {
464         // Optional InventoryAction operation that is run on the client
465         // to make lag less apparent.
466
467         Inventory *inv_from = mgr->getInventory(from_inv);
468         Inventory *inv_to = mgr->getInventory(to_inv);
469         if(!inv_from || !inv_to)
470                 return;
471
472         InventoryLocation current_player;
473         current_player.setCurrentPlayer();
474         Inventory *inv_player = mgr->getInventory(current_player);
475         if(inv_from != inv_player || inv_to != inv_player)
476                 return;
477
478         InventoryList *list_from = inv_from->getList(from_list);
479         InventoryList *list_to = inv_to->getList(to_list);
480         if(!list_from || !list_to)
481                 return;
482
483         list_from->moveItem(from_i, list_to, to_i, count);
484
485         mgr->setInventoryModified(from_inv);
486         if(inv_from != inv_to)
487                 mgr->setInventoryModified(to_inv);
488 }
489
490 /*
491         IDropAction
492 */
493
494 IDropAction::IDropAction(std::istream &is)
495 {
496         std::string ts;
497
498         std::getline(is, ts, ' ');
499         count = stoi(ts);
500
501         std::getline(is, ts, ' ');
502         from_inv.deSerialize(ts);
503
504         std::getline(is, from_list, ' ');
505
506         std::getline(is, ts, ' ');
507         from_i = stoi(ts);
508 }
509
510 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
511 {
512         Inventory *inv_from = mgr->getInventory(from_inv);
513         
514         if(!inv_from){
515                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
516                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
517                 return;
518         }
519
520         InventoryList *list_from = inv_from->getList(from_list);
521
522         /*
523                 If a list doesn't exist or the source item doesn't exist
524         */
525         if(!list_from){
526                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
527                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
528                 return;
529         }
530         if(list_from->getItem(from_i).empty())
531         {
532                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
533                                 <<"from_inv=\""<<from_inv.dump()<<"\""
534                                 <<", from_list=\""<<from_list<<"\""
535                                 <<" from_i="<<from_i<<std::endl;
536                 return;
537         }
538
539         /*
540                 Do not handle rollback if inventory is player's
541         */
542         bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
543
544         /*
545                 Collect information of endpoints
546         */
547
548         int take_count = list_from->getItem(from_i).count;
549         if(count != 0 && count < take_count)
550                 take_count = count;
551         int src_can_take_count = take_count;
552
553         // Source is detached
554         if(from_inv.type == InventoryLocation::DETACHED)
555         {
556                 ItemStack src_item = list_from->getItem(from_i);
557                 src_item.count = take_count;
558                 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
559                                 from_inv.name, from_list, from_i, src_item, player);
560         }
561
562         // Source is nodemeta
563         if(from_inv.type == InventoryLocation::NODEMETA)
564         {
565                 ItemStack src_item = list_from->getItem(from_i);
566                 src_item.count = take_count;
567                 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
568                                 from_inv.p, from_list, from_i, src_item, player);
569         }
570
571         if(src_can_take_count != -1 && src_can_take_count < take_count)
572                 take_count = src_can_take_count;
573         
574         int actually_dropped_count = 0;
575
576         ItemStack src_item = list_from->getItem(from_i);
577
578         // Drop the item
579         ItemStack item1 = list_from->getItem(from_i);
580         item1.count = take_count;
581         if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
582                                 player->getBasePosition() + v3f(0,1,0)))
583         {
584                 actually_dropped_count = take_count - item1.count;
585
586                 if(actually_dropped_count == 0){
587                         infostream<<"Actually dropped no items"<<std::endl;
588                         return;
589                 }
590                 
591                 // If source isn't infinite
592                 if(src_can_take_count != -1){
593                         // Take item from source list
594                         ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
595
596                         if(item2.count != actually_dropped_count)
597                                 errorstream<<"Could not take dropped count of items"<<std::endl;
598
599                         mgr->setInventoryModified(from_inv);
600                 }
601         }
602
603         infostream<<"IDropAction::apply(): dropped "
604                         <<" from inv=\""<<from_inv.dump()<<"\""
605                         <<" list=\""<<from_list<<"\""
606                         <<" i="<<from_i
607                         <<std::endl;
608         
609         src_item.count = actually_dropped_count;
610
611         /*
612                 Report drop to endpoints
613         */
614         
615         // Source is detached
616         if(from_inv.type == InventoryLocation::DETACHED)
617         {
618                 PLAYER_TO_SA(player)->detached_inventory_OnTake(
619                                 from_inv.name, from_list, from_i, src_item, player);
620         }
621
622         // Source is nodemeta
623         if(from_inv.type == InventoryLocation::NODEMETA)
624         {
625                 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
626                                 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         // Get the InventoryList in which we will operate
773         InventoryList *clist = inv->getList("craft");
774         if(!clist)
775                 return false;
776
777         // Mangle crafting grid to an another format
778         CraftInput ci;
779         ci.method = CRAFT_METHOD_NORMAL;
780         ci.width = clist->getWidth() ? clist->getWidth() : 3;
781         for(u16 i=0; i<clist->getSize(); i++)
782                 ci.items.push_back(clist->getItem(i));
783
784         // Find out what is crafted and add it to result item slot
785         CraftOutput co;
786         bool found = gamedef->getCraftDefManager()->getCraftResult(
787                         ci, co, decrementInput, gamedef);
788         if(found)
789                 result.deSerialize(co.item, gamedef->getItemDefManager());
790
791         if(found && decrementInput)
792         {
793                 // CraftInput has been changed, apply changes in clist
794                 for(u16 i=0; i<clist->getSize(); i++)
795                 {
796                         clist->changeItem(i, ci.items[i]);
797                 }
798         }
799
800         return found;
801 }
802