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);
308 // Then try all the empty ones
309 for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
310 if (list_to->getItem(dest_i).empty()) {
312 apply(mgr, player, gamedef);
319 caused_by_move_somewhere = false;
320 move_somewhere = true;
324 if ((u16)to_i > list_to->getSize()) {
325 infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
327 << ", size=" << list_to->getSize() << std::endl;
331 Do not handle rollback if both inventories are that of the same player
333 bool ignore_rollback = (
334 from_inv.type == InventoryLocation::PLAYER &&
338 Collect information of endpoints
341 ItemStack src_item = list_from->getItem(from_i);
343 src_item.count = count;
344 if (src_item.empty())
347 int src_can_take_count = 0xffff;
348 int dst_can_put_count = 0xffff;
350 // this is needed for swapping items inside one inventory to work
352 bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
353 && restitem.count == src_item.count
354 && !caused_by_move_somewhere;
356 // Shift-click: Cannot fill this stack, proceed with next slot
357 if (caused_by_move_somewhere && restitem.count == src_item.count)
361 // Swap will affect the entire stack if it can performed.
362 src_item = list_from->getItem(from_i);
363 count = src_item.count;
366 if (from_inv == to_inv) {
367 // Move action within the same inventory
368 src_can_take_count = allowMove(src_item.count, player);
370 bool swap_expected = allow_swap;
371 allow_swap = allow_swap
372 && (src_can_take_count == -1 || src_can_take_count >= src_item.count);
374 int try_put_count = list_to->getItem(to_i).count;
376 dst_can_put_count = allowMove(try_put_count, player);
377 allow_swap = allow_swap
378 && (dst_can_put_count == -1 || dst_can_put_count >= try_put_count);
381 dst_can_put_count = src_can_take_count;
383 if (swap_expected != allow_swap)
384 src_can_take_count = dst_can_put_count = 0;
386 // Take from one inventory, put into another
387 dst_can_put_count = allowPut(src_item, player);
388 src_can_take_count = allowTake(src_item, player);
390 bool swap_expected = allow_swap;
391 allow_swap = allow_swap
392 && (src_can_take_count == -1 || src_can_take_count >= src_item.count)
393 && (dst_can_put_count == -1 || dst_can_put_count >= src_item.count);
394 // A swap is expected, which means that we have to
395 // run the "allow" callbacks a second time with swapped inventories
397 ItemStack dst_item = list_to->getItem(to_i);
400 int src_can_take = allowPut(dst_item, player);
401 int dst_can_put = allowTake(dst_item, player);
402 allow_swap = allow_swap
403 && (src_can_take == -1 || src_can_take >= dst_item.count)
404 && (dst_can_put == -1 || dst_can_put >= dst_item.count);
407 if (swap_expected != allow_swap)
408 src_can_take_count = dst_can_put_count = 0;
411 int old_count = count;
413 /* Modify count according to collected data */
414 count = src_item.count;
415 if (src_can_take_count != -1 && count > src_can_take_count)
416 count = src_can_take_count;
417 if (dst_can_put_count != -1 && count > dst_can_put_count)
418 count = dst_can_put_count;
419 /* Limit according to source item count */
420 if (count > list_from->getItem(from_i).count)
421 count = list_from->getItem(from_i).count;
423 /* If no items will be moved, don't go further */
425 // Undo client prediction. See 'clientApply'
426 if (from_inv.type == InventoryLocation::PLAYER)
427 list_from->setModified();
429 if (to_inv.type == InventoryLocation::PLAYER)
430 list_to->setModified();
432 infostream<<"IMoveAction::apply(): move was completely disallowed:"
433 <<" count="<<old_count
434 <<" from inv=\""<<from_inv.dump()<<"\""
435 <<" list=\""<<from_list<<"\""
437 <<" to inv=\""<<to_inv.dump()<<"\""
438 <<" list=\""<<to_list<<"\""
444 src_item = list_from->getItem(from_i);
445 src_item.count = count;
446 ItemStack from_stack_was = list_from->getItem(from_i);
447 ItemStack to_stack_was = list_to->getItem(to_i);
452 If something is wrong (source item is empty, destination is the
453 same as source), nothing happens
455 bool did_swap = false;
456 move_count = list_from->moveItem(from_i,
457 list_to, to_i, count, allow_swap, &did_swap);
458 assert(allow_swap == did_swap);
460 // If source is infinite, reset it's stack
461 if (src_can_take_count == -1) {
462 // For the caused_by_move_somewhere == true case we didn't force-put the item,
463 // which guarantees there is no leftover, and code below would duplicate the
464 // (not replaced) to_stack_was item.
465 if (!caused_by_move_somewhere) {
466 // If destination stack is of different type and there are leftover
467 // items, attempt to put the leftover items to a different place in the
468 // destination inventory.
469 // The client-side GUI will try to guess if this happens.
470 if (from_stack_was.name != to_stack_was.name) {
471 for (u32 i = 0; i < list_to->getSize(); i++) {
472 if (list_to->getItem(i).empty()) {
473 list_to->changeItem(i, to_stack_was);
479 if (move_count > 0 || did_swap) {
480 list_from->deleteItem(from_i);
481 list_from->addItem(from_i, from_stack_was);
484 // If destination is infinite, reset it's stack and take count from source
485 if (dst_can_put_count == -1) {
486 list_to->deleteItem(to_i);
487 list_to->addItem(to_i, to_stack_was);
488 list_from->deleteItem(from_i);
489 list_from->addItem(from_i, from_stack_was);
490 list_from->takeItem(from_i, count);
493 infostream << "IMoveAction::apply(): moved"
494 << " msom=" << move_somewhere
495 << " caused=" << caused_by_move_somewhere
496 << " count=" << count
497 << " from inv=\"" << from_inv.dump() << "\""
498 << " list=\"" << from_list << "\""
500 << " to inv=\"" << to_inv.dump() << "\""
501 << " list=\"" << to_list << "\""
505 // If we are inside the move somewhere loop, we don't need to report
506 // anything if nothing happened (perhaps we don't need to report
507 // anything for caused_by_move_somewhere == true, but this way its safer)
508 if (caused_by_move_somewhere && move_count == 0)
512 Record rollback information
514 if (!ignore_rollback && gamedef->rollback()) {
515 IRollbackManager *rollback = gamedef->rollback();
517 // If source is not infinite, record item take
518 if (src_can_take_count != -1) {
519 RollbackAction action;
522 std::ostringstream os(std::ios::binary);
523 from_inv.serialize(os);
526 action.setModifyInventoryStack(loc, from_list, from_i, false,
528 rollback->reportAction(action);
530 // If destination is not infinite, record item put
531 if (dst_can_put_count != -1) {
532 RollbackAction action;
535 std::ostringstream os(std::ios::binary);
536 to_inv.serialize(os);
539 action.setModifyInventoryStack(loc, to_list, to_i, true,
541 rollback->reportAction(action);
546 Report move to endpoints
549 // Source = destination => move
550 if (from_inv == to_inv) {
551 onMove(count, player);
553 // Item is now placed in source list
554 src_item = list_from->getItem(from_i);
556 onMove(src_item.count, player);
559 mgr->setInventoryModified(from_inv);
561 onPutAndOnTake(src_item, player);
563 // Item is now placed in source list
564 src_item = list_from->getItem(from_i);
566 onPutAndOnTake(src_item, player);
569 mgr->setInventoryModified(to_inv);
570 mgr->setInventoryModified(from_inv);
574 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
576 // Optional InventoryAction operation that is run on the client
577 // to make lag less apparent.
579 Inventory *inv_from = mgr->getInventory(from_inv);
580 Inventory *inv_to = mgr->getInventory(to_inv);
581 if (!inv_from || !inv_to)
584 InventoryLocation current_player;
585 current_player.setCurrentPlayer();
586 Inventory *inv_player = mgr->getInventory(current_player);
587 if (inv_from != inv_player || inv_to != inv_player)
590 InventoryList *list_from = inv_from->getList(from_list);
591 InventoryList *list_to = inv_to->getList(to_list);
592 if (!list_from || !list_to)
596 list_from->moveItem(from_i, list_to, to_i, count);
598 list_from->moveItemSomewhere(from_i, list_to, count);
600 mgr->setInventoryModified(from_inv);
601 if (inv_from != inv_to)
602 mgr->setInventoryModified(to_inv);
609 IDropAction::IDropAction(std::istream &is)
613 std::getline(is, ts, ' ');
616 std::getline(is, ts, ' ');
617 from_inv.deSerialize(ts);
619 std::getline(is, from_list, ' ');
621 std::getline(is, ts, ' ');
625 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
627 Inventory *inv_from = mgr->getInventory(from_inv);
630 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
631 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
635 InventoryList *list_from = inv_from->getList(from_list);
638 If a list doesn't exist or the source item doesn't exist
641 infostream<<"IDropAction::apply(): FAIL: source list not found: "
642 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
645 if (list_from->getItem(from_i).empty()) {
646 infostream<<"IDropAction::apply(): FAIL: source item not found: "
647 <<"from_inv=\""<<from_inv.dump()<<"\""
648 <<", from_list=\""<<from_list<<"\""
649 <<" from_i="<<from_i<<std::endl;
654 Do not handle rollback if inventory is player's
656 bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
659 Collect information of endpoints
662 int take_count = list_from->getItem(from_i).count;
663 if (count != 0 && count < take_count)
665 int src_can_take_count = take_count;
667 ItemStack src_item = list_from->getItem(from_i);
668 src_item.count = take_count;
670 // Run callbacks depending on source inventory
671 switch (from_inv.type) {
672 case InventoryLocation::DETACHED:
673 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
674 *this, src_item, player);
676 case InventoryLocation::NODEMETA:
677 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
678 *this, src_item, player);
680 case InventoryLocation::PLAYER:
681 src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake(
682 *this, src_item, player);
688 if (src_can_take_count != -1 && src_can_take_count < take_count)
689 take_count = src_can_take_count;
691 // Update item due executed callbacks
692 src_item = list_from->getItem(from_i);
695 ItemStack item1 = list_from->getItem(from_i);
696 item1.count = take_count;
697 if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
698 player->getBasePosition())) {
699 int actually_dropped_count = take_count - item1.count;
701 if (actually_dropped_count == 0) {
702 infostream<<"Actually dropped no items"<<std::endl;
704 // Revert client prediction. See 'clientApply'
705 if (from_inv.type == InventoryLocation::PLAYER)
706 list_from->setModified();
710 // If source isn't infinite
711 if (src_can_take_count != -1) {
712 // Take item from source list
713 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
715 if (item2.count != actually_dropped_count)
716 errorstream<<"Could not take dropped count of items"<<std::endl;
719 src_item.count = actually_dropped_count;
720 mgr->setInventoryModified(from_inv);
723 infostream<<"IDropAction::apply(): dropped "
724 <<" from inv=\""<<from_inv.dump()<<"\""
725 <<" list=\""<<from_list<<"\""
731 Report drop to endpoints
734 switch (from_inv.type) {
735 case InventoryLocation::DETACHED:
736 PLAYER_TO_SA(player)->detached_inventory_OnTake(
737 *this, src_item, player);
739 case InventoryLocation::NODEMETA:
740 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
741 *this, src_item, player);
743 case InventoryLocation::PLAYER:
744 PLAYER_TO_SA(player)->player_inventory_OnTake(
745 *this, src_item, player);
752 Record rollback information
754 if (!ignore_src_rollback && gamedef->rollback()) {
755 IRollbackManager *rollback = gamedef->rollback();
757 // If source is not infinite, record item take
758 if (src_can_take_count != -1) {
759 RollbackAction action;
762 std::ostringstream os(std::ios::binary);
763 from_inv.serialize(os);
766 action.setModifyInventoryStack(loc, from_list, from_i,
768 rollback->reportAction(action);
773 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
775 // Optional InventoryAction operation that is run on the client
776 // to make lag less apparent.
778 Inventory *inv_from = mgr->getInventory(from_inv);
782 InventoryLocation current_player;
783 current_player.setCurrentPlayer();
784 Inventory *inv_player = mgr->getInventory(current_player);
785 if (inv_from != inv_player)
788 InventoryList *list_from = inv_from->getList(from_list);
793 list_from->changeItem(from_i, ItemStack());
795 list_from->takeItem(from_i, count);
797 mgr->setInventoryModified(from_inv);
804 ICraftAction::ICraftAction(std::istream &is)
808 std::getline(is, ts, ' ');
811 std::getline(is, ts, ' ');
812 craft_inv.deSerialize(ts);
815 void ICraftAction::apply(InventoryManager *mgr,
816 ServerActiveObject *player, IGameDef *gamedef)
818 Inventory *inv_craft = mgr->getInventory(craft_inv);
821 infostream << "ICraftAction::apply(): FAIL: inventory not found: "
822 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
826 InventoryList *list_craft = inv_craft->getList("craft");
827 InventoryList *list_craftresult = inv_craft->getList("craftresult");
828 InventoryList *list_main = inv_craft->getList("main");
831 If a list doesn't exist or the source item doesn't exist
834 infostream << "ICraftAction::apply(): FAIL: craft list not found: "
835 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
838 if (!list_craftresult) {
839 infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
840 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
843 if (list_craftresult->getSize() < 1) {
844 infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
845 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
850 ItemStack craftresultitem;
851 int count_remaining = count;
852 std::vector<ItemStack> output_replacements;
853 getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
854 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
855 bool found = !crafted.empty();
857 while (found && list_craftresult->itemFits(0, crafted)) {
858 InventoryList saved_craft_list = *list_craft;
860 std::vector<ItemStack> temp;
861 // Decrement input and add crafting output
862 getCraftingResult(inv_craft, crafted, temp, true, gamedef);
863 PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
864 list_craftresult->addItem(0, crafted);
865 mgr->setInventoryModified(craft_inv);
867 // Add the new replacements to the list
868 IItemDefManager *itemdef = gamedef->getItemDefManager();
869 for (auto &itemstack : temp) {
870 for (auto &output_replacement : output_replacements) {
871 if (itemstack.name == output_replacement.name) {
872 itemstack = output_replacement.addItem(itemstack, itemdef);
873 if (itemstack.empty())
877 output_replacements.push_back(itemstack);
880 actionstream << player->getDescription()
882 << crafted.getItemString()
886 if (count_remaining == 1)
889 if (count_remaining > 1)
892 // Get next crafting result
893 getCraftingResult(inv_craft, crafted, temp, false, gamedef);
894 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
895 found = !crafted.empty();
898 // Put the replacements in the inventory or drop them on the floor, if
899 // the inventory is full
900 for (auto &output_replacement : output_replacements) {
902 output_replacement = list_main->addItem(output_replacement);
903 if (output_replacement.empty())
905 u16 count = output_replacement.count;
907 PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player,
908 player->getBasePosition());
909 if (count >= output_replacement.count) {
910 errorstream << "Couldn't drop replacement stack " <<
911 output_replacement.getItemString() << " because drop loop didn't "
912 "decrease count." << std::endl;
916 } while (!output_replacement.empty());
919 infostream<<"ICraftAction::apply(): crafted "
920 <<" craft_inv=\""<<craft_inv.dump()<<"\""
924 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
926 // Optional InventoryAction operation that is run on the client
927 // to make lag less apparent.
932 bool getCraftingResult(Inventory *inv, ItemStack &result,
933 std::vector<ItemStack> &output_replacements,
934 bool decrementInput, IGameDef *gamedef)
938 // Get the InventoryList in which we will operate
939 InventoryList *clist = inv->getList("craft");
943 // Mangle crafting grid to an another format
945 ci.method = CRAFT_METHOD_NORMAL;
946 ci.width = clist->getWidth() ? clist->getWidth() : 3;
947 for (u16 i=0; i < clist->getSize(); i++)
948 ci.items.push_back(clist->getItem(i));
950 // Find out what is crafted and add it to result item slot
952 bool found = gamedef->getCraftDefManager()->getCraftResult(
953 ci, co, output_replacements, decrementInput, gamedef);
955 result.deSerialize(co.item, gamedef->getItemDefManager());
957 if (found && decrementInput) {
958 // CraftInput has been changed, apply changes in clist
959 for (u16 i=0; i < clist->getSize(); i++) {
960 clist->changeItem(i, ci.items[i]);