]> git.lizzy.rs Git - dragonfireclient.git/blob - src/inventorymanager.cpp
Disable mesh cache by default
[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 "settings.h"
26 #include "craftdef.h"
27 #include "rollback_interface.h"
28 #include "strfnd.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                 FATAL_ERROR("Unhandled inventory location type");
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                 a = new IMoveAction(is, false);
126         } else if (type == "MoveSomewhere") {
127                 a = new IMoveAction(is, true);
128         } else if (type == "Drop") {
129                 a = new IDropAction(is);
130         } else if(type == "Craft") {
131                 a = new ICraftAction(is);
132         }
133
134         return a;
135 }
136
137 /*
138         IMoveAction
139 */
140
141 IMoveAction::IMoveAction(std::istream &is, bool somewhere)
142 {
143         std::string ts;
144         move_somewhere = somewhere;
145         caused_by_move_somewhere = false;
146         move_count = 0;
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         if (!somewhere) {
165                 std::getline(is, ts, ' ');
166                 to_i = stoi(ts);
167         }
168 }
169
170 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
171 {
172         Inventory *inv_from = mgr->getInventory(from_inv);
173         Inventory *inv_to = mgr->getInventory(to_inv);
174         
175         if(!inv_from){
176                 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
177                                 <<"from_inv=\""<<from_inv.dump()<<"\""
178                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
179                 return;
180         }
181         if(!inv_to){
182                 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
183                                 <<"from_inv=\""<<from_inv.dump()<<"\""
184                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
185                 return;
186         }
187
188         InventoryList *list_from = inv_from->getList(from_list);
189         InventoryList *list_to = inv_to->getList(to_list);
190
191         /*
192                 If a list doesn't exist or the source item doesn't exist
193         */
194         if(!list_from){
195                 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
196                                 <<"from_inv=\""<<from_inv.dump()<<"\""
197                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
198                 return;
199         }
200         if(!list_to){
201                 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
202                                 <<"to_inv=\""<<to_inv.dump()<<"\""
203                                 <<", to_list=\""<<to_list<<"\""<<std::endl;
204                 return;
205         }
206
207         if (move_somewhere) {
208                 s16 old_to_i = to_i;
209                 u16 old_count = count;
210                 caused_by_move_somewhere = true;
211                 move_somewhere = false;
212
213                 infostream << "IMoveAction::apply(): moving item somewhere"
214                         << " msom=" << move_somewhere
215                         << " count=" << count
216                         << " from inv=\"" << from_inv.dump() << "\""
217                         << " list=\"" << from_list << "\""
218                         << " i=" << from_i
219                         << " to inv=\"" << to_inv.dump() << "\""
220                         << " list=\"" << to_list << "\""
221                         << std::endl;
222
223                 // Try to add the item to destination list
224                 s16 dest_size = list_to->getSize();
225                 // First try all the non-empty slots
226                 for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
227                         if (!list_to->getItem(dest_i).empty()) {
228                                 to_i = dest_i;
229                                 apply(mgr, player, gamedef);
230                                 count -= move_count;
231                         }
232                 }
233
234                 // Then try all the empty ones
235                 for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
236                         if (list_to->getItem(dest_i).empty()) {
237                                 to_i = dest_i;
238                                 apply(mgr, player, gamedef);
239                                 count -= move_count;
240                         }
241                 }
242
243                 to_i = old_to_i;
244                 count = old_count;
245                 caused_by_move_somewhere = false;
246                 move_somewhere = true;
247                 return;
248         }
249         /*
250                 Do not handle rollback if both inventories are that of the same player
251         */
252         bool ignore_rollback = (
253                 from_inv.type == InventoryLocation::PLAYER &&
254                 to_inv.type == InventoryLocation::PLAYER &&
255                 from_inv.name == to_inv.name);
256
257         /*
258                 Collect information of endpoints
259         */
260
261         int try_take_count = count;
262         if(try_take_count == 0)
263                 try_take_count = list_from->getItem(from_i).count;
264
265         int src_can_take_count = 0xffff;
266         int dst_can_put_count = 0xffff;
267         
268         /* Query detached inventories */
269
270         // Move occurs in the same detached inventory
271         if(from_inv.type == InventoryLocation::DETACHED &&
272                         to_inv.type == InventoryLocation::DETACHED &&
273                         from_inv.name == to_inv.name)
274         {
275                 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove(
276                                 from_inv.name, from_list, from_i,
277                                 to_list, to_i, try_take_count, player);
278                 dst_can_put_count = src_can_take_count;
279         }
280         else
281         {
282                 // Destination is detached
283                 if(to_inv.type == InventoryLocation::DETACHED)
284                 {
285                         ItemStack src_item = list_from->getItem(from_i);
286                         src_item.count = try_take_count;
287                         dst_can_put_count = PLAYER_TO_SA(player)->detached_inventory_AllowPut(
288                                         to_inv.name, to_list, to_i, src_item, player);
289                 }
290                 // Source is detached
291                 if(from_inv.type == InventoryLocation::DETACHED)
292                 {
293                         ItemStack src_item = list_from->getItem(from_i);
294                         src_item.count = try_take_count;
295                         src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
296                                         from_inv.name, from_list, from_i, src_item, player);
297                 }
298         }
299
300         /* Query node metadata inventories */
301
302         // Both endpoints are nodemeta
303         // Move occurs in the same nodemeta inventory
304         if(from_inv.type == InventoryLocation::NODEMETA &&
305                         to_inv.type == InventoryLocation::NODEMETA &&
306                         from_inv.p == to_inv.p)
307         {
308                 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove(
309                                 from_inv.p, from_list, from_i,
310                                 to_list, to_i, try_take_count, player);
311                 dst_can_put_count = src_can_take_count;
312         }
313         else
314         {
315                 // Destination is nodemeta
316                 if(to_inv.type == InventoryLocation::NODEMETA)
317                 {
318                         ItemStack src_item = list_from->getItem(from_i);
319                         src_item.count = try_take_count;
320                         dst_can_put_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut(
321                                         to_inv.p, to_list, to_i, src_item, player);
322                 }
323                 // Source is nodemeta
324                 if(from_inv.type == InventoryLocation::NODEMETA)
325                 {
326                         ItemStack src_item = list_from->getItem(from_i);
327                         src_item.count = try_take_count;
328                         src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
329                                         from_inv.p, from_list, from_i, src_item, player);
330                 }
331         }
332
333         int old_count = count;
334         
335         /* Modify count according to collected data */
336         count = try_take_count;
337         if(src_can_take_count != -1 && count > src_can_take_count)
338                 count = src_can_take_count;
339         if(dst_can_put_count != -1 && count > dst_can_put_count)
340                 count = dst_can_put_count;
341         /* Limit according to source item count */
342         if(count > list_from->getItem(from_i).count)
343                 count = list_from->getItem(from_i).count;
344         
345         /* If no items will be moved, don't go further */
346         if(count == 0)
347         {
348                 infostream<<"IMoveAction::apply(): move was completely disallowed:"
349                                 <<" count="<<old_count
350                                 <<" from inv=\""<<from_inv.dump()<<"\""
351                                 <<" list=\""<<from_list<<"\""
352                                 <<" i="<<from_i
353                                 <<" to inv=\""<<to_inv.dump()<<"\""
354                                 <<" list=\""<<to_list<<"\""
355                                 <<" i="<<to_i
356                                 <<std::endl;
357                 return;
358         }
359
360         ItemStack src_item = list_from->getItem(from_i);
361         src_item.count = count;
362         ItemStack from_stack_was = list_from->getItem(from_i);
363         ItemStack to_stack_was = list_to->getItem(to_i);
364
365         /*
366                 Perform actual move
367
368                 If something is wrong (source item is empty, destination is the
369                 same as source), nothing happens
370         */
371         move_count = list_from->moveItem(from_i,
372                 list_to, to_i, count, !caused_by_move_somewhere);
373
374         // If source is infinite, reset it's stack
375         if(src_can_take_count == -1){
376                 // If destination stack is of different type and there are leftover
377                 // items, attempt to put the leftover items to a different place in the
378                 // destination inventory.
379                 // The client-side GUI will try to guess if this happens.
380                 if(from_stack_was.name != to_stack_was.name){
381                         for(u32 i=0; i<list_to->getSize(); i++){
382                                 if(list_to->getItem(i).empty()){
383                                         list_to->changeItem(i, to_stack_was);
384                                         break;
385                                 }
386                         }
387                 }
388                 list_from->deleteItem(from_i);
389                 list_from->addItem(from_i, from_stack_was);
390         }
391         // If destination is infinite, reset it's stack and take count from source
392         if(dst_can_put_count == -1){
393                 list_to->deleteItem(to_i);
394                 list_to->addItem(to_i, to_stack_was);
395                 list_from->deleteItem(from_i);
396                 list_from->addItem(from_i, from_stack_was);
397                 list_from->takeItem(from_i, count);
398         }
399
400         infostream << "IMoveAction::apply(): moved"
401                         << " msom=" << move_somewhere
402                         << " caused=" << caused_by_move_somewhere
403                         << " count=" << count
404                         << " from inv=\"" << from_inv.dump() << "\""
405                         << " list=\"" << from_list << "\""
406                         << " i=" << from_i
407                         << " to inv=\"" << to_inv.dump() << "\""
408                         << " list=\"" << to_list << "\""
409                         << " i=" << to_i
410                         << std::endl;
411
412         /*
413                 Record rollback information
414         */
415         if(!ignore_rollback && gamedef->rollback())
416         {
417                 IRollbackManager *rollback = gamedef->rollback();
418
419                 // If source is not infinite, record item take
420                 if(src_can_take_count != -1){
421                         RollbackAction action;
422                         std::string loc;
423                         {
424                                 std::ostringstream os(std::ios::binary);
425                                 from_inv.serialize(os);
426                                 loc = os.str();
427                         }
428                         action.setModifyInventoryStack(loc, from_list, from_i, false,
429                                         src_item);
430                         rollback->reportAction(action);
431                 }
432                 // If destination is not infinite, record item put
433                 if(dst_can_put_count != -1){
434                         RollbackAction action;
435                         std::string loc;
436                         {
437                                 std::ostringstream os(std::ios::binary);
438                                 to_inv.serialize(os);
439                                 loc = os.str();
440                         }
441                         action.setModifyInventoryStack(loc, to_list, to_i, true,
442                                         src_item);
443                         rollback->reportAction(action);
444                 }
445         }
446
447         /*
448                 Report move to endpoints
449         */
450         
451         /* Detached inventories */
452
453         // Both endpoints are same detached
454         if(from_inv.type == InventoryLocation::DETACHED &&
455                         to_inv.type == InventoryLocation::DETACHED &&
456                         from_inv.name == to_inv.name)
457         {
458                 PLAYER_TO_SA(player)->detached_inventory_OnMove(
459                                 from_inv.name, from_list, from_i,
460                                 to_list, to_i, count, player);
461         }
462         else
463         {
464                 // Destination is detached
465                 if(to_inv.type == InventoryLocation::DETACHED)
466                 {
467                         PLAYER_TO_SA(player)->detached_inventory_OnPut(
468                                         to_inv.name, to_list, to_i, src_item, player);
469                 }
470                 // Source is detached
471                 if(from_inv.type == InventoryLocation::DETACHED)
472                 {
473                         PLAYER_TO_SA(player)->detached_inventory_OnTake(
474                                         from_inv.name, from_list, from_i, src_item, player);
475                 }
476         }
477
478         /* Node metadata inventories */
479
480         // Both endpoints are same nodemeta
481         if(from_inv.type == InventoryLocation::NODEMETA &&
482                         to_inv.type == InventoryLocation::NODEMETA &&
483                         from_inv.p == to_inv.p)
484         {
485                 PLAYER_TO_SA(player)->nodemeta_inventory_OnMove(
486                                 from_inv.p, from_list, from_i,
487                                 to_list, to_i, count, player);
488         }
489         else{
490                 // Destination is nodemeta
491                 if(to_inv.type == InventoryLocation::NODEMETA)
492                 {
493                         PLAYER_TO_SA(player)->nodemeta_inventory_OnPut(
494                                         to_inv.p, to_list, to_i, src_item, player);
495                 }
496                 // Source is nodemeta
497                 else if(from_inv.type == InventoryLocation::NODEMETA)
498                 {
499                         PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
500                                         from_inv.p, from_list, from_i, src_item, player);
501                 }
502         }
503         
504         mgr->setInventoryModified(from_inv, false);
505         if(inv_from != inv_to)
506                 mgr->setInventoryModified(to_inv, false);
507 }
508
509 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
510 {
511         // Optional InventoryAction operation that is run on the client
512         // to make lag less apparent.
513
514         Inventory *inv_from = mgr->getInventory(from_inv);
515         Inventory *inv_to = mgr->getInventory(to_inv);
516         if(!inv_from || !inv_to)
517                 return;
518
519         InventoryLocation current_player;
520         current_player.setCurrentPlayer();
521         Inventory *inv_player = mgr->getInventory(current_player);
522         if(inv_from != inv_player || inv_to != inv_player)
523                 return;
524
525         InventoryList *list_from = inv_from->getList(from_list);
526         InventoryList *list_to = inv_to->getList(to_list);
527         if(!list_from || !list_to)
528                 return;
529
530         if (!move_somewhere)
531                 list_from->moveItem(from_i, list_to, to_i, count);
532         else
533                 list_from->moveItemSomewhere(from_i, list_to, count);
534
535         mgr->setInventoryModified(from_inv);
536         if(inv_from != inv_to)
537                 mgr->setInventoryModified(to_inv);
538 }
539
540 /*
541         IDropAction
542 */
543
544 IDropAction::IDropAction(std::istream &is)
545 {
546         std::string ts;
547
548         std::getline(is, ts, ' ');
549         count = stoi(ts);
550
551         std::getline(is, ts, ' ');
552         from_inv.deSerialize(ts);
553
554         std::getline(is, from_list, ' ');
555
556         std::getline(is, ts, ' ');
557         from_i = stoi(ts);
558 }
559
560 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
561 {
562         Inventory *inv_from = mgr->getInventory(from_inv);
563         
564         if(!inv_from){
565                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
566                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
567                 return;
568         }
569
570         InventoryList *list_from = inv_from->getList(from_list);
571
572         /*
573                 If a list doesn't exist or the source item doesn't exist
574         */
575         if(!list_from){
576                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
577                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
578                 return;
579         }
580         if(list_from->getItem(from_i).empty())
581         {
582                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
583                                 <<"from_inv=\""<<from_inv.dump()<<"\""
584                                 <<", from_list=\""<<from_list<<"\""
585                                 <<" from_i="<<from_i<<std::endl;
586                 return;
587         }
588
589         /*
590                 Do not handle rollback if inventory is player's
591         */
592         bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
593
594         /*
595                 Collect information of endpoints
596         */
597
598         int take_count = list_from->getItem(from_i).count;
599         if(count != 0 && count < take_count)
600                 take_count = count;
601         int src_can_take_count = take_count;
602
603         // Source is detached
604         if(from_inv.type == InventoryLocation::DETACHED)
605         {
606                 ItemStack src_item = list_from->getItem(from_i);
607                 src_item.count = take_count;
608                 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
609                                 from_inv.name, from_list, from_i, src_item, player);
610         }
611
612         // Source is nodemeta
613         if(from_inv.type == InventoryLocation::NODEMETA)
614         {
615                 ItemStack src_item = list_from->getItem(from_i);
616                 src_item.count = take_count;
617                 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
618                                 from_inv.p, from_list, from_i, src_item, player);
619         }
620
621         if(src_can_take_count != -1 && src_can_take_count < take_count)
622                 take_count = src_can_take_count;
623         
624         int actually_dropped_count = 0;
625
626         ItemStack src_item = list_from->getItem(from_i);
627
628         // Drop the item
629         ItemStack item1 = list_from->getItem(from_i);
630         item1.count = take_count;
631         if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
632                                 player->getBasePosition() + v3f(0,1,0)))
633         {
634                 actually_dropped_count = take_count - item1.count;
635
636                 if(actually_dropped_count == 0){
637                         infostream<<"Actually dropped no items"<<std::endl;
638                         return;
639                 }
640                 
641                 // If source isn't infinite
642                 if(src_can_take_count != -1){
643                         // Take item from source list
644                         ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
645
646                         if(item2.count != actually_dropped_count)
647                                 errorstream<<"Could not take dropped count of items"<<std::endl;
648
649                         mgr->setInventoryModified(from_inv, false);
650                 }
651         }
652
653         infostream<<"IDropAction::apply(): dropped "
654                         <<" from inv=\""<<from_inv.dump()<<"\""
655                         <<" list=\""<<from_list<<"\""
656                         <<" i="<<from_i
657                         <<std::endl;
658         
659         src_item.count = actually_dropped_count;
660
661         /*
662                 Report drop to endpoints
663         */
664         
665         // Source is detached
666         if(from_inv.type == InventoryLocation::DETACHED)
667         {
668                 PLAYER_TO_SA(player)->detached_inventory_OnTake(
669                                 from_inv.name, from_list, from_i, src_item, player);
670         }
671
672         // Source is nodemeta
673         if(from_inv.type == InventoryLocation::NODEMETA)
674         {
675                 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
676                                 from_inv.p, from_list, from_i, src_item, player);
677         }
678
679         /*
680                 Record rollback information
681         */
682         if(!ignore_src_rollback && gamedef->rollback())
683         {
684                 IRollbackManager *rollback = gamedef->rollback();
685
686                 // If source is not infinite, record item take
687                 if(src_can_take_count != -1){
688                         RollbackAction action;
689                         std::string loc;
690                         {
691                                 std::ostringstream os(std::ios::binary);
692                                 from_inv.serialize(os);
693                                 loc = os.str();
694                         }
695                         action.setModifyInventoryStack(loc, from_list, from_i,
696                                         false, src_item);
697                         rollback->reportAction(action);
698                 }
699         }
700 }
701
702 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
703 {
704         // Optional InventoryAction operation that is run on the client
705         // to make lag less apparent.
706
707         Inventory *inv_from = mgr->getInventory(from_inv);
708         if(!inv_from)
709                 return;
710
711         InventoryLocation current_player;
712         current_player.setCurrentPlayer();
713         Inventory *inv_player = mgr->getInventory(current_player);
714         if(inv_from != inv_player)
715                 return;
716
717         InventoryList *list_from = inv_from->getList(from_list);
718         if(!list_from)
719                 return;
720
721         if(count == 0)
722                 list_from->changeItem(from_i, ItemStack());
723         else
724                 list_from->takeItem(from_i, count);
725
726         mgr->setInventoryModified(from_inv);
727 }
728
729 /*
730         ICraftAction
731 */
732
733 ICraftAction::ICraftAction(std::istream &is)
734 {
735         std::string ts;
736
737         std::getline(is, ts, ' ');
738         count = stoi(ts);
739
740         std::getline(is, ts, ' ');
741         craft_inv.deSerialize(ts);
742 }
743
744 void ICraftAction::apply(InventoryManager *mgr,
745         ServerActiveObject *player, IGameDef *gamedef)
746 {
747         Inventory *inv_craft = mgr->getInventory(craft_inv);
748         
749         if (!inv_craft) {
750                 infostream << "ICraftAction::apply(): FAIL: inventory not found: "
751                                 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
752                 return;
753         }
754
755         InventoryList *list_craft = inv_craft->getList("craft");
756         InventoryList *list_craftresult = inv_craft->getList("craftresult");
757         InventoryList *list_main = inv_craft->getList("main");
758
759         /*
760                 If a list doesn't exist or the source item doesn't exist
761         */
762         if (!list_craft) {
763                 infostream << "ICraftAction::apply(): FAIL: craft list not found: "
764                                 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
765                 return;
766         }
767         if (!list_craftresult) {
768                 infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
769                                 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
770                 return;
771         }
772         if (list_craftresult->getSize() < 1) {
773                 infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
774                                 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
775                 return;
776         }
777
778         ItemStack crafted;
779         ItemStack craftresultitem;
780         int count_remaining = count;
781         std::vector<ItemStack> output_replacements;
782         getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
783         PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
784         bool found = !crafted.empty();
785
786         while (found && list_craftresult->itemFits(0, crafted)) {
787                 InventoryList saved_craft_list = *list_craft;
788
789                 std::vector<ItemStack> temp;
790                 // Decrement input and add crafting output
791                 getCraftingResult(inv_craft, crafted, temp, true, gamedef);
792                 PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
793                 list_craftresult->addItem(0, crafted);
794                 mgr->setInventoryModified(craft_inv);
795
796                 // Add the new replacements to the list
797                 IItemDefManager *itemdef = gamedef->getItemDefManager();
798                 for (std::vector<ItemStack>::iterator it = temp.begin();
799                                 it != temp.end(); it++) {
800                         for (std::vector<ItemStack>::iterator jt = output_replacements.begin();
801                                         jt != output_replacements.end(); jt++) {
802                                 if (it->name == jt->name) {
803                                         *it = jt->addItem(*it, itemdef);
804                                         if (it->empty())
805                                                 continue;
806                                 }
807                         }
808                         output_replacements.push_back(*it);
809                 }
810
811                 actionstream << player->getDescription()
812                                 << " crafts "
813                                 << crafted.getItemString()
814                                 << std::endl;
815
816                 // Decrement counter
817                 if (count_remaining == 1)
818                         break;
819                 else if (count_remaining > 1)
820                         count_remaining--;
821
822                 // Get next crafting result
823                 found = getCraftingResult(inv_craft, crafted, temp, false, gamedef);
824                 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
825                 found = !crafted.empty();
826         }
827
828         // Put the replacements in the inventory or drop them on the floor, if
829         // the invenotry is full
830         for (std::vector<ItemStack>::iterator it = output_replacements.begin();
831                         it != output_replacements.end(); it++) {
832                 if (list_main)
833                         *it = list_main->addItem(*it);
834                 if (it->empty())
835                         continue;
836                 u16 count = it->count;
837                 do {
838                         PLAYER_TO_SA(player)->item_OnDrop(*it, player,
839                                 player->getBasePosition() + v3f(0,1,0));
840                         if (count >= it->count) {
841                                 errorstream << "Couldn't drop replacement stack " <<
842                                         it->getItemString() << " because drop loop didn't "
843                                         "decrease count." << std::endl;
844
845                                 break;
846                         }
847                 } while (!it->empty());
848         }
849
850         infostream<<"ICraftAction::apply(): crafted "
851                         <<" craft_inv=\""<<craft_inv.dump()<<"\""
852                         <<std::endl;
853 }
854
855 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
856 {
857         // Optional InventoryAction operation that is run on the client
858         // to make lag less apparent.
859 }
860
861
862 // Crafting helper
863 bool getCraftingResult(Inventory *inv, ItemStack& result,
864                 std::vector<ItemStack> &output_replacements,
865                 bool decrementInput, IGameDef *gamedef)
866 {
867         DSTACK(__FUNCTION_NAME);
868         
869         result.clear();
870
871         // Get the InventoryList in which we will operate
872         InventoryList *clist = inv->getList("craft");
873         if(!clist)
874                 return false;
875
876         // Mangle crafting grid to an another format
877         CraftInput ci;
878         ci.method = CRAFT_METHOD_NORMAL;
879         ci.width = clist->getWidth() ? clist->getWidth() : 3;
880         for(u16 i=0; i<clist->getSize(); i++)
881                 ci.items.push_back(clist->getItem(i));
882
883         // Find out what is crafted and add it to result item slot
884         CraftOutput co;
885         bool found = gamedef->getCraftDefManager()->getCraftResult(
886                         ci, co, output_replacements, decrementInput, gamedef);
887         if(found)
888                 result.deSerialize(co.item, gamedef->getItemDefManager());
889
890         if(found && decrementInput)
891         {
892                 // CraftInput has been changed, apply changes in clist
893                 for(u16 i=0; i<clist->getSize(); i++)
894                 {
895                         clist->changeItem(i, ci.items[i]);
896                 }
897         }
898
899         return found;
900 }
901