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