3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
20 #include "inventorymanager.h"
23 #include "serverenvironment.h"
24 #include "scripting_server.h"
25 #include "server/serveractiveobject.h"
28 #include "rollback_interface.h"
29 #include "util/strfnd.h"
30 #include "util/basic_macros.h"
32 #define PLAYER_TO_SA(p) p->getEnv()->getScriptIface()
38 std::string InventoryLocation::dump() const
40 std::ostringstream os(std::ios::binary);
45 void InventoryLocation::serialize(std::ostream &os) const
48 case InventoryLocation::UNDEFINED:
51 case InventoryLocation::CURRENT_PLAYER:
54 case InventoryLocation::PLAYER:
57 case InventoryLocation::NODEMETA:
58 os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
60 case InventoryLocation::DETACHED:
61 os<<"detached:"<<name;
64 FATAL_ERROR("Unhandled inventory location type");
68 void InventoryLocation::deSerialize(std::istream &is)
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;
82 std::getline(is, pos, '\n');
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');
91 infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
92 throw SerializationError("Unknown InventoryLocation type");
96 void InventoryLocation::deSerialize(const std::string &s)
98 std::istringstream is(s, std::ios::binary);
106 InventoryAction *InventoryAction::deSerialize(std::istream &is)
109 std::getline(is, type, ' ');
111 InventoryAction *a = nullptr;
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);
130 IMoveAction::IMoveAction(std::istream &is, bool somewhere) :
131 move_somewhere(somewhere)
135 std::getline(is, ts, ' ');
138 std::getline(is, ts, ' ');
139 from_inv.deSerialize(ts);
141 std::getline(is, from_list, ' ');
143 std::getline(is, ts, ' ');
146 std::getline(is, ts, ' ');
147 to_inv.deSerialize(ts);
149 std::getline(is, to_list, ' ');
152 std::getline(is, ts, ' ');
157 void IMoveAction::swapDirections()
159 std::swap(from_inv, to_inv);
160 std::swap(from_list, to_list);
161 std::swap(from_i, to_i);
164 void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const
166 ServerScripting *sa = PLAYER_TO_SA(player);
167 if (to_inv.type == InventoryLocation::DETACHED)
168 sa->detached_inventory_OnPut(*this, src_item, player);
169 else if (to_inv.type == InventoryLocation::NODEMETA)
170 sa->nodemeta_inventory_OnPut(*this, src_item, player);
171 else if (to_inv.type == InventoryLocation::PLAYER)
172 sa->player_inventory_OnPut(*this, src_item, player);
176 if (from_inv.type == InventoryLocation::DETACHED)
177 sa->detached_inventory_OnTake(*this, src_item, player);
178 else if (from_inv.type == InventoryLocation::NODEMETA)
179 sa->nodemeta_inventory_OnTake(*this, src_item, player);
180 else if (from_inv.type == InventoryLocation::PLAYER)
181 sa->player_inventory_OnTake(*this, src_item, player);
186 void IMoveAction::onMove(int count, ServerActiveObject *player) const
188 ServerScripting *sa = PLAYER_TO_SA(player);
189 if (from_inv.type == InventoryLocation::DETACHED)
190 sa->detached_inventory_OnMove(*this, count, player);
191 else if (from_inv.type == InventoryLocation::NODEMETA)
192 sa->nodemeta_inventory_OnMove(*this, count, player);
193 else if (from_inv.type == InventoryLocation::PLAYER)
194 sa->player_inventory_OnMove(*this, count, player);
199 int IMoveAction::allowPut(const ItemStack &dst_item, ServerActiveObject *player) const
201 ServerScripting *sa = PLAYER_TO_SA(player);
202 int dst_can_put_count = 0xffff;
203 if (to_inv.type == InventoryLocation::DETACHED)
204 dst_can_put_count = sa->detached_inventory_AllowPut(*this, dst_item, player);
205 else if (to_inv.type == InventoryLocation::NODEMETA)
206 dst_can_put_count = sa->nodemeta_inventory_AllowPut(*this, dst_item, player);
207 else if (to_inv.type == InventoryLocation::PLAYER)
208 dst_can_put_count = sa->player_inventory_AllowPut(*this, dst_item, player);
211 return dst_can_put_count;
214 int IMoveAction::allowTake(const ItemStack &src_item, ServerActiveObject *player) const
216 ServerScripting *sa = PLAYER_TO_SA(player);
217 int src_can_take_count = 0xffff;
218 if (from_inv.type == InventoryLocation::DETACHED)
219 src_can_take_count = sa->detached_inventory_AllowTake(*this, src_item, player);
220 else if (from_inv.type == InventoryLocation::NODEMETA)
221 src_can_take_count = sa->nodemeta_inventory_AllowTake(*this, src_item, player);
222 else if (from_inv.type == InventoryLocation::PLAYER)
223 src_can_take_count = sa->player_inventory_AllowTake(*this, src_item, player);
226 return src_can_take_count;
229 int IMoveAction::allowMove(int try_take_count, ServerActiveObject *player) const
231 ServerScripting *sa = PLAYER_TO_SA(player);
232 int src_can_take_count = 0xffff;
233 if (from_inv.type == InventoryLocation::DETACHED)
234 src_can_take_count = sa->detached_inventory_AllowMove(*this, try_take_count, player);
235 else if (from_inv.type == InventoryLocation::NODEMETA)
236 src_can_take_count = sa->nodemeta_inventory_AllowMove(*this, try_take_count, player);
237 else if (from_inv.type == InventoryLocation::PLAYER)
238 src_can_take_count = sa->player_inventory_AllowMove(*this, try_take_count, player);
241 return src_can_take_count;
244 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
246 Inventory *inv_from = mgr->getInventory(from_inv);
247 Inventory *inv_to = mgr->getInventory(to_inv);
250 infostream << "IMoveAction::apply(): FAIL: source inventory not found: "
251 << "from_inv=\""<<from_inv.dump() << "\""
252 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
256 infostream << "IMoveAction::apply(): FAIL: destination inventory not found: "
257 << "from_inv=\"" << from_inv.dump() << "\""
258 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
262 InventoryList *list_from = inv_from->getList(from_list);
263 InventoryList *list_to = inv_to->getList(to_list);
266 If a list doesn't exist or the source item doesn't exist
269 infostream << "IMoveAction::apply(): FAIL: source list not found: "
270 << "from_inv=\"" << from_inv.dump() << "\""
271 << ", from_list=\"" << from_list << "\"" << std::endl;
275 infostream << "IMoveAction::apply(): FAIL: destination list not found: "
276 << "to_inv=\""<<to_inv.dump() << "\""
277 << ", to_list=\"" << to_list << "\"" << std::endl;
281 if (move_somewhere) {
283 u16 old_count = count;
284 caused_by_move_somewhere = true;
285 move_somewhere = false;
287 infostream << "IMoveAction::apply(): moving item somewhere"
288 << " msom=" << move_somewhere
289 << " count=" << count
290 << " from inv=\"" << from_inv.dump() << "\""
291 << " list=\"" << from_list << "\""
293 << " to inv=\"" << to_inv.dump() << "\""
294 << " list=\"" << to_list << "\""
297 // Try to add the item to destination list
298 s16 dest_size = list_to->getSize();
299 // First try all the non-empty slots
300 for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
301 if (!list_to->getItem(dest_i).empty()) {
303 apply(mgr, player, gamedef);
304 assert(move_count <= count);
309 // Then try all the empty ones
310 for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
311 if (list_to->getItem(dest_i).empty()) {
313 apply(mgr, player, gamedef);
320 caused_by_move_somewhere = false;
321 move_somewhere = true;
325 if ((u16)to_i > list_to->getSize()) {
326 infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
328 << ", size=" << list_to->getSize() << std::endl;
332 Do not handle rollback if both inventories are that of the same player
334 bool ignore_rollback = (
335 from_inv.type == InventoryLocation::PLAYER &&
339 Collect information of endpoints
342 ItemStack src_item = list_from->getItem(from_i);
343 if (count > 0 && count < src_item.count)
344 src_item.count = count;
345 if (src_item.empty())
348 int src_can_take_count = 0xffff;
349 int dst_can_put_count = 0xffff;
351 // this is needed for swapping items inside one inventory to work
353 bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
354 && restitem.count == src_item.count
355 && !caused_by_move_somewhere;
356 move_count = src_item.count - restitem.count;
358 // Shift-click: Cannot fill this stack, proceed with next slot
359 if (caused_by_move_somewhere && move_count == 0) {
364 // Swap will affect the entire stack if it can performed.
365 src_item = list_from->getItem(from_i);
366 count = src_item.count;
369 if (from_inv == to_inv) {
370 // Move action within the same inventory
371 src_can_take_count = allowMove(src_item.count, player);
373 bool swap_expected = allow_swap;
374 allow_swap = allow_swap
375 && (src_can_take_count == -1 || src_can_take_count >= src_item.count);
377 int try_put_count = list_to->getItem(to_i).count;
379 dst_can_put_count = allowMove(try_put_count, player);
380 allow_swap = allow_swap
381 && (dst_can_put_count == -1 || dst_can_put_count >= try_put_count);
384 dst_can_put_count = src_can_take_count;
386 if (swap_expected != allow_swap)
387 src_can_take_count = dst_can_put_count = 0;
389 // Take from one inventory, put into another
390 int src_item_count = src_item.count;
391 if (caused_by_move_somewhere)
392 // When moving somewhere: temporarily use the actual movable stack
393 // size to ensure correct callback execution.
394 src_item.count = move_count;
395 dst_can_put_count = allowPut(src_item, player);
396 src_can_take_count = allowTake(src_item, player);
397 if (caused_by_move_somewhere)
398 // Reset source item count
399 src_item.count = src_item_count;
400 bool swap_expected = allow_swap;
401 allow_swap = allow_swap
402 && (src_can_take_count == -1 || src_can_take_count >= src_item.count)
403 && (dst_can_put_count == -1 || dst_can_put_count >= src_item.count);
404 // A swap is expected, which means that we have to
405 // run the "allow" callbacks a second time with swapped inventories
407 ItemStack dst_item = list_to->getItem(to_i);
410 int src_can_take = allowPut(dst_item, player);
411 int dst_can_put = allowTake(dst_item, player);
412 allow_swap = allow_swap
413 && (src_can_take == -1 || src_can_take >= dst_item.count)
414 && (dst_can_put == -1 || dst_can_put >= dst_item.count);
417 if (swap_expected != allow_swap)
418 src_can_take_count = dst_can_put_count = 0;
421 int old_count = count;
423 /* Modify count according to collected data */
424 count = src_item.count;
425 if (src_can_take_count != -1 && count > src_can_take_count)
426 count = src_can_take_count;
427 if (dst_can_put_count != -1 && count > dst_can_put_count)
428 count = dst_can_put_count;
430 /* Limit according to source item count */
431 if (count > list_from->getItem(from_i).count)
432 count = list_from->getItem(from_i).count;
434 /* If no items will be moved, don't go further */
436 if (caused_by_move_somewhere)
437 // Set move count to zero, as no items have been moved
440 // Undo client prediction. See 'clientApply'
441 if (from_inv.type == InventoryLocation::PLAYER)
442 list_from->setModified();
444 if (to_inv.type == InventoryLocation::PLAYER)
445 list_to->setModified();
447 infostream<<"IMoveAction::apply(): move was completely disallowed:"
448 <<" count="<<old_count
449 <<" from inv=\""<<from_inv.dump()<<"\""
450 <<" list=\""<<from_list<<"\""
452 <<" to inv=\""<<to_inv.dump()<<"\""
453 <<" list=\""<<to_list<<"\""
460 src_item = list_from->getItem(from_i);
461 src_item.count = count;
462 ItemStack from_stack_was = list_from->getItem(from_i);
463 ItemStack to_stack_was = list_to->getItem(to_i);
468 If something is wrong (source item is empty, destination is the
469 same as source), nothing happens
471 bool did_swap = false;
472 move_count = list_from->moveItem(from_i,
473 list_to, to_i, count, allow_swap, &did_swap);
474 if (caused_by_move_somewhere)
476 assert(allow_swap == did_swap);
478 // If source is infinite, reset it's stack
479 if (src_can_take_count == -1) {
480 // For the caused_by_move_somewhere == true case we didn't force-put the item,
481 // which guarantees there is no leftover, and code below would duplicate the
482 // (not replaced) to_stack_was item.
483 if (!caused_by_move_somewhere) {
484 // If destination stack is of different type and there are leftover
485 // items, attempt to put the leftover items to a different place in the
486 // destination inventory.
487 // The client-side GUI will try to guess if this happens.
488 if (from_stack_was.name != to_stack_was.name) {
489 for (u32 i = 0; i < list_to->getSize(); i++) {
490 if (list_to->getItem(i).empty()) {
491 list_to->changeItem(i, to_stack_was);
497 if (move_count > 0 || did_swap) {
498 list_from->deleteItem(from_i);
499 list_from->addItem(from_i, from_stack_was);
502 // If destination is infinite, reset it's stack and take count from source
503 if (dst_can_put_count == -1) {
504 list_to->deleteItem(to_i);
505 list_to->addItem(to_i, to_stack_was);
506 list_from->deleteItem(from_i);
507 list_from->addItem(from_i, from_stack_was);
508 list_from->takeItem(from_i, count);
511 infostream << "IMoveAction::apply(): moved"
512 << " msom=" << move_somewhere
513 << " caused=" << caused_by_move_somewhere
514 << " count=" << count
515 << " from inv=\"" << from_inv.dump() << "\""
516 << " list=\"" << from_list << "\""
518 << " to inv=\"" << to_inv.dump() << "\""
519 << " list=\"" << to_list << "\""
523 // If we are inside the move somewhere loop, we don't need to report
524 // anything if nothing happened
525 if (caused_by_move_somewhere && move_count == 0)
529 Record rollback information
531 if (!ignore_rollback && gamedef->rollback()) {
532 IRollbackManager *rollback = gamedef->rollback();
534 // If source is not infinite, record item take
535 if (src_can_take_count != -1) {
536 RollbackAction action;
539 std::ostringstream os(std::ios::binary);
540 from_inv.serialize(os);
543 action.setModifyInventoryStack(loc, from_list, from_i, false,
545 rollback->reportAction(action);
547 // If destination is not infinite, record item put
548 if (dst_can_put_count != -1) {
549 RollbackAction action;
552 std::ostringstream os(std::ios::binary);
553 to_inv.serialize(os);
556 action.setModifyInventoryStack(loc, to_list, to_i, true,
558 rollback->reportAction(action);
563 Report move to endpoints
566 // Source = destination => move
567 if (from_inv == to_inv) {
568 onMove(count, player);
570 // Item is now placed in source list
571 src_item = list_from->getItem(from_i);
573 onMove(src_item.count, player);
576 mgr->setInventoryModified(from_inv);
578 int src_item_count = src_item.count;
579 if (caused_by_move_somewhere)
580 // When moving somewhere: temporarily use the actual movable stack
581 // size to ensure correct callback execution.
582 src_item.count = move_count;
583 onPutAndOnTake(src_item, player);
584 if (caused_by_move_somewhere)
585 // Reset source item count
586 src_item.count = src_item_count;
588 // Item is now placed in source list
589 src_item = list_from->getItem(from_i);
591 onPutAndOnTake(src_item, player);
594 mgr->setInventoryModified(to_inv);
595 mgr->setInventoryModified(from_inv);
599 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
601 // Optional InventoryAction operation that is run on the client
602 // to make lag less apparent.
604 Inventory *inv_from = mgr->getInventory(from_inv);
605 Inventory *inv_to = mgr->getInventory(to_inv);
606 if (!inv_from || !inv_to)
609 InventoryLocation current_player;
610 current_player.setCurrentPlayer();
611 Inventory *inv_player = mgr->getInventory(current_player);
612 if (inv_from != inv_player || inv_to != inv_player)
615 InventoryList *list_from = inv_from->getList(from_list);
616 InventoryList *list_to = inv_to->getList(to_list);
617 if (!list_from || !list_to)
621 list_from->moveItem(from_i, list_to, to_i, count);
623 list_from->moveItemSomewhere(from_i, list_to, count);
625 mgr->setInventoryModified(from_inv);
626 if (inv_from != inv_to)
627 mgr->setInventoryModified(to_inv);
634 IDropAction::IDropAction(std::istream &is)
638 std::getline(is, ts, ' ');
641 std::getline(is, ts, ' ');
642 from_inv.deSerialize(ts);
644 std::getline(is, from_list, ' ');
646 std::getline(is, ts, ' ');
650 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
652 Inventory *inv_from = mgr->getInventory(from_inv);
655 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
656 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
660 InventoryList *list_from = inv_from->getList(from_list);
663 If a list doesn't exist or the source item doesn't exist
666 infostream<<"IDropAction::apply(): FAIL: source list not found: "
667 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
670 if (list_from->getItem(from_i).empty()) {
671 infostream<<"IDropAction::apply(): FAIL: source item not found: "
672 <<"from_inv=\""<<from_inv.dump()<<"\""
673 <<", from_list=\""<<from_list<<"\""
674 <<" from_i="<<from_i<<std::endl;
679 Do not handle rollback if inventory is player's
681 bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
684 Collect information of endpoints
687 int take_count = list_from->getItem(from_i).count;
688 if (count != 0 && count < take_count)
690 int src_can_take_count = take_count;
692 ItemStack src_item = list_from->getItem(from_i);
693 src_item.count = take_count;
695 // Run callbacks depending on source inventory
696 switch (from_inv.type) {
697 case InventoryLocation::DETACHED:
698 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
699 *this, src_item, player);
701 case InventoryLocation::NODEMETA:
702 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
703 *this, src_item, player);
705 case InventoryLocation::PLAYER:
706 src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake(
707 *this, src_item, player);
713 if (src_can_take_count != -1 && src_can_take_count < take_count)
714 take_count = src_can_take_count;
716 // Update item due executed callbacks
717 src_item = list_from->getItem(from_i);
720 ItemStack item1 = list_from->getItem(from_i);
721 item1.count = take_count;
722 if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
723 player->getBasePosition())) {
724 int actually_dropped_count = take_count - item1.count;
726 if (actually_dropped_count == 0) {
727 infostream<<"Actually dropped no items"<<std::endl;
729 // Revert client prediction. See 'clientApply'
730 if (from_inv.type == InventoryLocation::PLAYER)
731 list_from->setModified();
735 // If source isn't infinite
736 if (src_can_take_count != -1) {
737 // Take item from source list
738 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
740 if (item2.count != actually_dropped_count)
741 errorstream<<"Could not take dropped count of items"<<std::endl;
744 src_item.count = actually_dropped_count;
745 mgr->setInventoryModified(from_inv);
748 infostream<<"IDropAction::apply(): dropped "
749 <<" from inv=\""<<from_inv.dump()<<"\""
750 <<" list=\""<<from_list<<"\""
756 Report drop to endpoints
759 switch (from_inv.type) {
760 case InventoryLocation::DETACHED:
761 PLAYER_TO_SA(player)->detached_inventory_OnTake(
762 *this, src_item, player);
764 case InventoryLocation::NODEMETA:
765 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
766 *this, src_item, player);
768 case InventoryLocation::PLAYER:
769 PLAYER_TO_SA(player)->player_inventory_OnTake(
770 *this, src_item, player);
777 Record rollback information
779 if (!ignore_src_rollback && gamedef->rollback()) {
780 IRollbackManager *rollback = gamedef->rollback();
782 // If source is not infinite, record item take
783 if (src_can_take_count != -1) {
784 RollbackAction action;
787 std::ostringstream os(std::ios::binary);
788 from_inv.serialize(os);
791 action.setModifyInventoryStack(loc, from_list, from_i,
793 rollback->reportAction(action);
798 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
800 // Optional InventoryAction operation that is run on the client
801 // to make lag less apparent.
803 Inventory *inv_from = mgr->getInventory(from_inv);
807 InventoryLocation current_player;
808 current_player.setCurrentPlayer();
809 Inventory *inv_player = mgr->getInventory(current_player);
810 if (inv_from != inv_player)
813 InventoryList *list_from = inv_from->getList(from_list);
818 list_from->changeItem(from_i, ItemStack());
820 list_from->takeItem(from_i, count);
822 mgr->setInventoryModified(from_inv);
829 ICraftAction::ICraftAction(std::istream &is)
833 std::getline(is, ts, ' ');
836 std::getline(is, ts, ' ');
837 craft_inv.deSerialize(ts);
840 void ICraftAction::apply(InventoryManager *mgr,
841 ServerActiveObject *player, IGameDef *gamedef)
843 Inventory *inv_craft = mgr->getInventory(craft_inv);
846 infostream << "ICraftAction::apply(): FAIL: inventory not found: "
847 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
851 InventoryList *list_craft = inv_craft->getList("craft");
852 InventoryList *list_craftresult = inv_craft->getList("craftresult");
853 InventoryList *list_main = inv_craft->getList("main");
856 If a list doesn't exist or the source item doesn't exist
859 infostream << "ICraftAction::apply(): FAIL: craft list not found: "
860 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
863 if (!list_craftresult) {
864 infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
865 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
868 if (list_craftresult->getSize() < 1) {
869 infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
870 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
875 ItemStack craftresultitem;
876 int count_remaining = count;
877 std::vector<ItemStack> output_replacements;
878 getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
879 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
880 bool found = !crafted.empty();
882 while (found && list_craftresult->itemFits(0, crafted)) {
883 InventoryList saved_craft_list = *list_craft;
885 std::vector<ItemStack> temp;
886 // Decrement input and add crafting output
887 getCraftingResult(inv_craft, crafted, temp, true, gamedef);
888 PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
889 list_craftresult->addItem(0, crafted);
890 mgr->setInventoryModified(craft_inv);
892 // Add the new replacements to the list
893 IItemDefManager *itemdef = gamedef->getItemDefManager();
894 for (auto &itemstack : temp) {
895 for (auto &output_replacement : output_replacements) {
896 if (itemstack.name == output_replacement.name) {
897 itemstack = output_replacement.addItem(itemstack, itemdef);
898 if (itemstack.empty())
902 output_replacements.push_back(itemstack);
905 actionstream << player->getDescription()
907 << crafted.getItemString()
911 if (count_remaining == 1)
914 if (count_remaining > 1)
917 // Get next crafting result
918 getCraftingResult(inv_craft, crafted, temp, false, gamedef);
919 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
920 found = !crafted.empty();
923 // Put the replacements in the inventory or drop them on the floor, if
924 // the inventory is full
925 for (auto &output_replacement : output_replacements) {
927 output_replacement = list_main->addItem(output_replacement);
928 if (output_replacement.empty())
930 u16 count = output_replacement.count;
932 PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player,
933 player->getBasePosition());
934 if (count >= output_replacement.count) {
935 errorstream << "Couldn't drop replacement stack " <<
936 output_replacement.getItemString() << " because drop loop didn't "
937 "decrease count." << std::endl;
941 } while (!output_replacement.empty());
944 infostream<<"ICraftAction::apply(): crafted "
945 <<" craft_inv=\""<<craft_inv.dump()<<"\""
949 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
951 // Optional InventoryAction operation that is run on the client
952 // to make lag less apparent.
957 bool getCraftingResult(Inventory *inv, ItemStack &result,
958 std::vector<ItemStack> &output_replacements,
959 bool decrementInput, IGameDef *gamedef)
963 // Get the InventoryList in which we will operate
964 InventoryList *clist = inv->getList("craft");
968 // Mangle crafting grid to an another format
970 ci.method = CRAFT_METHOD_NORMAL;
971 ci.width = clist->getWidth() ? clist->getWidth() : 3;
972 for (u16 i=0; i < clist->getSize(); i++)
973 ci.items.push_back(clist->getItem(i));
975 // Find out what is crafted and add it to result item slot
977 bool found = gamedef->getCraftDefManager()->getCraftResult(
978 ci, co, output_replacements, decrementInput, gamedef);
980 result.deSerialize(co.item, gamedef->getItemDefManager());
982 if (found && decrementInput) {
983 // CraftInput has been changed, apply changes in clist
984 for (u16 i=0; i < clist->getSize(); i++) {
985 clist->changeItem(i, ci.items[i]);