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 "serverobject.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(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::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
159 Inventory *inv_from = mgr->getInventory(from_inv);
160 Inventory *inv_to = mgr->getInventory(to_inv);
163 infostream << "IMoveAction::apply(): FAIL: source inventory not found: "
164 << "from_inv=\""<<from_inv.dump() << "\""
165 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
169 infostream << "IMoveAction::apply(): FAIL: destination inventory not found: "
170 << "from_inv=\"" << from_inv.dump() << "\""
171 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
175 InventoryList *list_from = inv_from->getList(from_list);
176 InventoryList *list_to = inv_to->getList(to_list);
179 If a list doesn't exist or the source item doesn't exist
182 infostream << "IMoveAction::apply(): FAIL: source list not found: "
183 << "from_inv=\"" << from_inv.dump() << "\""
184 << ", from_list=\"" << from_list << "\"" << std::endl;
188 infostream << "IMoveAction::apply(): FAIL: destination list not found: "
189 << "to_inv=\""<<to_inv.dump() << "\""
190 << ", to_list=\"" << to_list << "\"" << std::endl;
194 if (move_somewhere) {
196 u16 old_count = count;
197 caused_by_move_somewhere = true;
198 move_somewhere = false;
200 infostream << "IMoveAction::apply(): moving item somewhere"
201 << " msom=" << move_somewhere
202 << " count=" << count
203 << " from inv=\"" << from_inv.dump() << "\""
204 << " list=\"" << from_list << "\""
206 << " to inv=\"" << to_inv.dump() << "\""
207 << " list=\"" << to_list << "\""
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()) {
216 apply(mgr, player, gamedef);
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()) {
225 apply(mgr, player, gamedef);
232 caused_by_move_somewhere = false;
233 move_somewhere = true;
237 if ((u16)to_i > list_to->getSize()) {
238 infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
240 << ", size=" << list_to->getSize() << std::endl;
244 Do not handle rollback if both inventories are that of the same player
246 bool ignore_rollback = (
247 from_inv.type == InventoryLocation::PLAYER &&
248 to_inv.type == InventoryLocation::PLAYER &&
249 from_inv.name == to_inv.name);
252 Collect information of endpoints
255 int try_take_count = count;
256 if (try_take_count == 0)
257 try_take_count = list_from->getItem(from_i).count;
259 int src_can_take_count = 0xffff;
260 int dst_can_put_count = 0xffff;
262 /* Query detached inventories */
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;
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);
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);
289 /* Query node metadata inventories */
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;
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);
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);
317 int old_count = count;
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;
329 /* If no items will be moved, don't go further */
331 infostream<<"IMoveAction::apply(): move was completely disallowed:"
332 <<" count="<<old_count
333 <<" from inv=\""<<from_inv.dump()<<"\""
334 <<" list=\""<<from_list<<"\""
336 <<" to inv=\""<<to_inv.dump()<<"\""
337 <<" list=\""<<to_list<<"\""
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);
351 If something is wrong (source item is empty, destination is the
352 same as source), nothing happens
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);
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);
377 if (move_count > 0 || did_swap) {
378 list_from->deleteItem(from_i);
379 list_from->addItem(from_i, from_stack_was);
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);
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 << "\""
398 << " to inv=\"" << to_inv.dump() << "\""
399 << " list=\"" << to_list << "\""
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)
410 Record rollback information
412 if (!ignore_rollback && gamedef->rollback()) {
413 IRollbackManager *rollback = gamedef->rollback();
415 // If source is not infinite, record item take
416 if (src_can_take_count != -1) {
417 RollbackAction action;
420 std::ostringstream os(std::ios::binary);
421 from_inv.serialize(os);
424 action.setModifyInventoryStack(loc, from_list, from_i, false,
426 rollback->reportAction(action);
428 // If destination is not infinite, record item put
429 if (dst_can_put_count != -1) {
430 RollbackAction action;
433 std::ostringstream os(std::ios::binary);
434 to_inv.serialize(os);
437 action.setModifyInventoryStack(loc, to_list, to_i, true,
439 rollback->reportAction(action);
444 Report move to endpoints
447 /* Detached inventories */
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);
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);
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);
469 /* Node metadata inventories */
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);
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);
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);
491 mgr->setInventoryModified(from_inv, false);
492 if (inv_from != inv_to)
493 mgr->setInventoryModified(to_inv, false);
496 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
498 // Optional InventoryAction operation that is run on the client
499 // to make lag less apparent.
501 Inventory *inv_from = mgr->getInventory(from_inv);
502 Inventory *inv_to = mgr->getInventory(to_inv);
503 if (!inv_from || !inv_to)
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)
512 InventoryList *list_from = inv_from->getList(from_list);
513 InventoryList *list_to = inv_to->getList(to_list);
514 if (!list_from || !list_to)
518 list_from->moveItem(from_i, list_to, to_i, count);
520 list_from->moveItemSomewhere(from_i, list_to, count);
522 mgr->setInventoryModified(from_inv);
523 if (inv_from != inv_to)
524 mgr->setInventoryModified(to_inv);
531 IDropAction::IDropAction(std::istream &is)
535 std::getline(is, ts, ' ');
538 std::getline(is, ts, ' ');
539 from_inv.deSerialize(ts);
541 std::getline(is, from_list, ' ');
543 std::getline(is, ts, ' ');
547 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
549 Inventory *inv_from = mgr->getInventory(from_inv);
552 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
553 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
557 InventoryList *list_from = inv_from->getList(from_list);
560 If a list doesn't exist or the source item doesn't exist
563 infostream<<"IDropAction::apply(): FAIL: source list not found: "
564 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
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;
576 Do not handle rollback if inventory is player's
578 bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
581 Collect information of endpoints
584 int take_count = list_from->getItem(from_i).count;
585 if (count != 0 && count < take_count)
587 int src_can_take_count = take_count;
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);
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);
605 if (src_can_take_count != -1 && src_can_take_count < take_count)
606 take_count = src_can_take_count;
608 int actually_dropped_count = 0;
610 ItemStack src_item = list_from->getItem(from_i);
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;
619 if (actually_dropped_count == 0) {
620 infostream<<"Actually dropped no items"<<std::endl;
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);
629 if (item2.count != actually_dropped_count)
630 errorstream<<"Could not take dropped count of items"<<std::endl;
632 mgr->setInventoryModified(from_inv, false);
636 infostream<<"IDropAction::apply(): dropped "
637 <<" from inv=\""<<from_inv.dump()<<"\""
638 <<" list=\""<<from_list<<"\""
642 src_item.count = actually_dropped_count;
645 Report drop to endpoints
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);
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);
661 Record rollback information
663 if (!ignore_src_rollback && gamedef->rollback()) {
664 IRollbackManager *rollback = gamedef->rollback();
666 // If source is not infinite, record item take
667 if (src_can_take_count != -1) {
668 RollbackAction action;
671 std::ostringstream os(std::ios::binary);
672 from_inv.serialize(os);
675 action.setModifyInventoryStack(loc, from_list, from_i,
677 rollback->reportAction(action);
682 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
684 // Optional InventoryAction operation that is run on the client
685 // to make lag less apparent.
687 Inventory *inv_from = mgr->getInventory(from_inv);
691 InventoryLocation current_player;
692 current_player.setCurrentPlayer();
693 Inventory *inv_player = mgr->getInventory(current_player);
694 if (inv_from != inv_player)
697 InventoryList *list_from = inv_from->getList(from_list);
702 list_from->changeItem(from_i, ItemStack());
704 list_from->takeItem(from_i, count);
706 mgr->setInventoryModified(from_inv);
713 ICraftAction::ICraftAction(std::istream &is)
717 std::getline(is, ts, ' ');
720 std::getline(is, ts, ' ');
721 craft_inv.deSerialize(ts);
724 void ICraftAction::apply(InventoryManager *mgr,
725 ServerActiveObject *player, IGameDef *gamedef)
727 Inventory *inv_craft = mgr->getInventory(craft_inv);
730 infostream << "ICraftAction::apply(): FAIL: inventory not found: "
731 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
735 InventoryList *list_craft = inv_craft->getList("craft");
736 InventoryList *list_craftresult = inv_craft->getList("craftresult");
737 InventoryList *list_main = inv_craft->getList("main");
740 If a list doesn't exist or the source item doesn't exist
743 infostream << "ICraftAction::apply(): FAIL: craft list not found: "
744 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
747 if (!list_craftresult) {
748 infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
749 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
752 if (list_craftresult->getSize() < 1) {
753 infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
754 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
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();
766 while (found && list_craftresult->itemFits(0, crafted)) {
767 InventoryList saved_craft_list = *list_craft;
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);
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())
786 output_replacements.push_back(itemstack);
789 actionstream << player->getDescription()
791 << crafted.getItemString()
795 if (count_remaining == 1)
798 if (count_remaining > 1)
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();
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) {
811 output_replacement = list_main->addItem(output_replacement);
812 if (output_replacement.empty())
814 u16 count = output_replacement.count;
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;
825 } while (!output_replacement.empty());
828 infostream<<"ICraftAction::apply(): crafted "
829 <<" craft_inv=\""<<craft_inv.dump()<<"\""
833 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
835 // Optional InventoryAction operation that is run on the client
836 // to make lag less apparent.
841 bool getCraftingResult(Inventory *inv, ItemStack &result,
842 std::vector<ItemStack> &output_replacements,
843 bool decrementInput, IGameDef *gamedef)
847 // Get the InventoryList in which we will operate
848 InventoryList *clist = inv->getList("craft");
852 // Mangle crafting grid to an another format
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));
859 // Find out what is crafted and add it to result item slot
861 bool found = gamedef->getCraftDefManager()->getCraftResult(
862 ci, co, output_replacements, decrementInput, gamedef);
864 result.deSerialize(co.item, gamedef->getItemDefManager());
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]);