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"
22 #include "serverenvironment.h"
23 #include "scripting_server.h"
24 #include "serverobject.h"
27 #include "rollback_interface.h"
28 #include "util/strfnd.h"
29 #include "util/basic_macros.h"
31 #define PLAYER_TO_SA(p) p->getEnv()->getScriptIface()
37 std::string InventoryLocation::dump() const
39 std::ostringstream os(std::ios::binary);
44 void InventoryLocation::serialize(std::ostream &os) const
47 case InventoryLocation::UNDEFINED:
50 case InventoryLocation::CURRENT_PLAYER:
53 case InventoryLocation::PLAYER:
56 case InventoryLocation::NODEMETA:
57 os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
59 case InventoryLocation::DETACHED:
60 os<<"detached:"<<name;
63 FATAL_ERROR("Unhandled inventory location type");
67 void InventoryLocation::deSerialize(std::istream &is)
70 std::getline(is, tname, ':');
71 if (tname == "undefined") {
72 type = InventoryLocation::UNDEFINED;
73 } else if (tname == "current_player") {
74 type = InventoryLocation::CURRENT_PLAYER;
75 } else if (tname == "player") {
76 type = InventoryLocation::PLAYER;
77 std::getline(is, name, '\n');
78 } else if (tname == "nodemeta") {
79 type = InventoryLocation::NODEMETA;
81 std::getline(is, pos, '\n');
83 p.X = stoi(fn.next(","));
84 p.Y = stoi(fn.next(","));
85 p.Z = stoi(fn.next(","));
86 } else if (tname == "detached") {
87 type = InventoryLocation::DETACHED;
88 std::getline(is, name, '\n');
90 infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
91 throw SerializationError("Unknown InventoryLocation type");
95 void InventoryLocation::deSerialize(std::string s)
97 std::istringstream is(s, std::ios::binary);
105 InventoryAction *InventoryAction::deSerialize(std::istream &is)
108 std::getline(is, type, ' ');
110 InventoryAction *a = nullptr;
112 if (type == "Move") {
113 a = new IMoveAction(is, false);
114 } else if (type == "MoveSomewhere") {
115 a = new IMoveAction(is, true);
116 } else if (type == "Drop") {
117 a = new IDropAction(is);
118 } else if (type == "Craft") {
119 a = new ICraftAction(is);
129 IMoveAction::IMoveAction(std::istream &is, bool somewhere) :
130 move_somewhere(somewhere)
134 std::getline(is, ts, ' ');
137 std::getline(is, ts, ' ');
138 from_inv.deSerialize(ts);
140 std::getline(is, from_list, ' ');
142 std::getline(is, ts, ' ');
145 std::getline(is, ts, ' ');
146 to_inv.deSerialize(ts);
148 std::getline(is, to_list, ' ');
151 std::getline(is, ts, ' ');
156 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
158 Inventory *inv_from = mgr->getInventory(from_inv);
159 Inventory *inv_to = mgr->getInventory(to_inv);
162 infostream << "IMoveAction::apply(): FAIL: source inventory not found: "
163 << "from_inv=\""<<from_inv.dump() << "\""
164 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
168 infostream << "IMoveAction::apply(): FAIL: destination inventory not found: "
169 << "from_inv=\"" << from_inv.dump() << "\""
170 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
174 InventoryList *list_from = inv_from->getList(from_list);
175 InventoryList *list_to = inv_to->getList(to_list);
178 If a list doesn't exist or the source item doesn't exist
181 infostream << "IMoveAction::apply(): FAIL: source list not found: "
182 << "from_inv=\"" << from_inv.dump() << "\""
183 << ", from_list=\"" << from_list << "\"" << std::endl;
187 infostream << "IMoveAction::apply(): FAIL: destination list not found: "
188 << "to_inv=\""<<to_inv.dump() << "\""
189 << ", to_list=\"" << to_list << "\"" << std::endl;
193 if (move_somewhere) {
195 u16 old_count = count;
196 caused_by_move_somewhere = true;
197 move_somewhere = false;
199 infostream << "IMoveAction::apply(): moving item somewhere"
200 << " msom=" << move_somewhere
201 << " count=" << count
202 << " from inv=\"" << from_inv.dump() << "\""
203 << " list=\"" << from_list << "\""
205 << " to inv=\"" << to_inv.dump() << "\""
206 << " list=\"" << to_list << "\""
209 // Try to add the item to destination list
210 s16 dest_size = list_to->getSize();
211 // First try all the non-empty slots
212 for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
213 if (!list_to->getItem(dest_i).empty()) {
215 apply(mgr, player, gamedef);
220 // Then try all the empty ones
221 for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
222 if (list_to->getItem(dest_i).empty()) {
224 apply(mgr, player, gamedef);
231 caused_by_move_somewhere = false;
232 move_somewhere = true;
236 if ((u16)to_i > list_to->getSize()) {
237 infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
239 << ", size=" << list_to->getSize() << std::endl;
243 Do not handle rollback if both inventories are that of the same player
245 bool ignore_rollback = (
246 from_inv.type == InventoryLocation::PLAYER &&
247 to_inv.type == InventoryLocation::PLAYER &&
248 from_inv.name == to_inv.name);
251 Collect information of endpoints
254 int try_take_count = count;
255 if (try_take_count == 0)
256 try_take_count = list_from->getItem(from_i).count;
258 int src_can_take_count = 0xffff;
259 int dst_can_put_count = 0xffff;
261 /* Query detached inventories */
263 // Move occurs in the same detached inventory
264 if (from_inv.type == InventoryLocation::DETACHED &&
265 to_inv.type == InventoryLocation::DETACHED &&
266 from_inv.name == to_inv.name) {
267 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove(
268 from_inv.name, from_list, from_i,
269 to_list, to_i, try_take_count, player);
270 dst_can_put_count = src_can_take_count;
272 // Destination is detached
273 if (to_inv.type == InventoryLocation::DETACHED) {
274 ItemStack src_item = list_from->getItem(from_i);
275 src_item.count = try_take_count;
276 dst_can_put_count = PLAYER_TO_SA(player)->detached_inventory_AllowPut(
277 to_inv.name, to_list, to_i, src_item, player);
279 // Source is detached
280 if (from_inv.type == InventoryLocation::DETACHED) {
281 ItemStack src_item = list_from->getItem(from_i);
282 src_item.count = try_take_count;
283 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
284 from_inv.name, from_list, from_i, src_item, player);
288 /* Query node metadata inventories */
290 // Both endpoints are nodemeta
291 // Move occurs in the same nodemeta inventory
292 if (from_inv.type == InventoryLocation::NODEMETA &&
293 to_inv.type == InventoryLocation::NODEMETA &&
294 from_inv.p == to_inv.p) {
295 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove(
296 from_inv.p, from_list, from_i,
297 to_list, to_i, try_take_count, player);
298 dst_can_put_count = src_can_take_count;
300 // Destination is nodemeta
301 if (to_inv.type == InventoryLocation::NODEMETA) {
302 ItemStack src_item = list_from->getItem(from_i);
303 src_item.count = try_take_count;
304 dst_can_put_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut(
305 to_inv.p, to_list, to_i, src_item, player);
307 // Source is nodemeta
308 if (from_inv.type == InventoryLocation::NODEMETA) {
309 ItemStack src_item = list_from->getItem(from_i);
310 src_item.count = try_take_count;
311 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
312 from_inv.p, from_list, from_i, src_item, player);
316 int old_count = count;
318 /* Modify count according to collected data */
319 count = try_take_count;
320 if (src_can_take_count != -1 && count > src_can_take_count)
321 count = src_can_take_count;
322 if (dst_can_put_count != -1 && count > dst_can_put_count)
323 count = dst_can_put_count;
324 /* Limit according to source item count */
325 if (count > list_from->getItem(from_i).count)
326 count = list_from->getItem(from_i).count;
328 /* If no items will be moved, don't go further */
330 infostream<<"IMoveAction::apply(): move was completely disallowed:"
331 <<" count="<<old_count
332 <<" from inv=\""<<from_inv.dump()<<"\""
333 <<" list=\""<<from_list<<"\""
335 <<" to inv=\""<<to_inv.dump()<<"\""
336 <<" list=\""<<to_list<<"\""
342 ItemStack src_item = list_from->getItem(from_i);
343 src_item.count = count;
344 ItemStack from_stack_was = list_from->getItem(from_i);
345 ItemStack to_stack_was = list_to->getItem(to_i);
350 If something is wrong (source item is empty, destination is the
351 same as source), nothing happens
353 bool did_swap = false;
354 move_count = list_from->moveItem(from_i,
355 list_to, to_i, count, !caused_by_move_somewhere, &did_swap);
357 // If source is infinite, reset it's stack
358 if (src_can_take_count == -1) {
359 // For the caused_by_move_somewhere == true case we didn't force-put the item,
360 // which guarantees there is no leftover, and code below would duplicate the
361 // (not replaced) to_stack_was item.
362 if (!caused_by_move_somewhere) {
363 // If destination stack is of different type and there are leftover
364 // items, attempt to put the leftover items to a different place in the
365 // destination inventory.
366 // The client-side GUI will try to guess if this happens.
367 if (from_stack_was.name != to_stack_was.name) {
368 for (u32 i = 0; i < list_to->getSize(); i++) {
369 if (list_to->getItem(i).empty()) {
370 list_to->changeItem(i, to_stack_was);
376 if (move_count > 0 || did_swap) {
377 list_from->deleteItem(from_i);
378 list_from->addItem(from_i, from_stack_was);
381 // If destination is infinite, reset it's stack and take count from source
382 if (dst_can_put_count == -1) {
383 list_to->deleteItem(to_i);
384 list_to->addItem(to_i, to_stack_was);
385 list_from->deleteItem(from_i);
386 list_from->addItem(from_i, from_stack_was);
387 list_from->takeItem(from_i, count);
390 infostream << "IMoveAction::apply(): moved"
391 << " msom=" << move_somewhere
392 << " caused=" << caused_by_move_somewhere
393 << " count=" << count
394 << " from inv=\"" << from_inv.dump() << "\""
395 << " list=\"" << from_list << "\""
397 << " to inv=\"" << to_inv.dump() << "\""
398 << " list=\"" << to_list << "\""
402 // If we are inside the move somewhere loop, we don't need to report
403 // anything if nothing happened (perhaps we don't need to report
404 // anything for caused_by_move_somewhere == true, but this way its safer)
405 if (caused_by_move_somewhere && move_count == 0)
409 Record rollback information
411 if (!ignore_rollback && gamedef->rollback()) {
412 IRollbackManager *rollback = gamedef->rollback();
414 // If source is not infinite, record item take
415 if (src_can_take_count != -1) {
416 RollbackAction action;
419 std::ostringstream os(std::ios::binary);
420 from_inv.serialize(os);
423 action.setModifyInventoryStack(loc, from_list, from_i, false,
425 rollback->reportAction(action);
427 // If destination is not infinite, record item put
428 if (dst_can_put_count != -1) {
429 RollbackAction action;
432 std::ostringstream os(std::ios::binary);
433 to_inv.serialize(os);
436 action.setModifyInventoryStack(loc, to_list, to_i, true,
438 rollback->reportAction(action);
443 Report move to endpoints
446 /* Detached inventories */
448 // Both endpoints are same detached
449 if (from_inv.type == InventoryLocation::DETACHED &&
450 to_inv.type == InventoryLocation::DETACHED &&
451 from_inv.name == to_inv.name) {
452 PLAYER_TO_SA(player)->detached_inventory_OnMove(
453 from_inv.name, from_list, from_i,
454 to_list, to_i, count, player);
456 // Destination is detached
457 if (to_inv.type == InventoryLocation::DETACHED) {
458 PLAYER_TO_SA(player)->detached_inventory_OnPut(
459 to_inv.name, to_list, to_i, src_item, player);
461 // Source is detached
462 if (from_inv.type == InventoryLocation::DETACHED) {
463 PLAYER_TO_SA(player)->detached_inventory_OnTake(
464 from_inv.name, from_list, from_i, src_item, player);
468 /* Node metadata inventories */
470 // Both endpoints are same nodemeta
471 if (from_inv.type == InventoryLocation::NODEMETA &&
472 to_inv.type == InventoryLocation::NODEMETA &&
473 from_inv.p == to_inv.p) {
474 PLAYER_TO_SA(player)->nodemeta_inventory_OnMove(
475 from_inv.p, from_list, from_i,
476 to_list, to_i, count, player);
478 // Destination is nodemeta
479 if (to_inv.type == InventoryLocation::NODEMETA) {
480 PLAYER_TO_SA(player)->nodemeta_inventory_OnPut(
481 to_inv.p, to_list, to_i, src_item, player);
483 // Source is nodemeta
484 else if (from_inv.type == InventoryLocation::NODEMETA) {
485 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
486 from_inv.p, from_list, from_i, src_item, player);
490 mgr->setInventoryModified(from_inv, false);
491 if (inv_from != inv_to)
492 mgr->setInventoryModified(to_inv, false);
495 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
497 // Optional InventoryAction operation that is run on the client
498 // to make lag less apparent.
500 Inventory *inv_from = mgr->getInventory(from_inv);
501 Inventory *inv_to = mgr->getInventory(to_inv);
502 if (!inv_from || !inv_to)
505 InventoryLocation current_player;
506 current_player.setCurrentPlayer();
507 Inventory *inv_player = mgr->getInventory(current_player);
508 if (inv_from != inv_player || inv_to != inv_player)
511 InventoryList *list_from = inv_from->getList(from_list);
512 InventoryList *list_to = inv_to->getList(to_list);
513 if (!list_from || !list_to)
517 list_from->moveItem(from_i, list_to, to_i, count);
519 list_from->moveItemSomewhere(from_i, list_to, count);
521 mgr->setInventoryModified(from_inv);
522 if (inv_from != inv_to)
523 mgr->setInventoryModified(to_inv);
530 IDropAction::IDropAction(std::istream &is)
534 std::getline(is, ts, ' ');
537 std::getline(is, ts, ' ');
538 from_inv.deSerialize(ts);
540 std::getline(is, from_list, ' ');
542 std::getline(is, ts, ' ');
546 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
548 Inventory *inv_from = mgr->getInventory(from_inv);
551 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
552 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
556 InventoryList *list_from = inv_from->getList(from_list);
559 If a list doesn't exist or the source item doesn't exist
562 infostream<<"IDropAction::apply(): FAIL: source list not found: "
563 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
566 if (list_from->getItem(from_i).empty()) {
567 infostream<<"IDropAction::apply(): FAIL: source item not found: "
568 <<"from_inv=\""<<from_inv.dump()<<"\""
569 <<", from_list=\""<<from_list<<"\""
570 <<" from_i="<<from_i<<std::endl;
575 Do not handle rollback if inventory is player's
577 bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
580 Collect information of endpoints
583 int take_count = list_from->getItem(from_i).count;
584 if (count != 0 && count < take_count)
586 int src_can_take_count = take_count;
588 // Source is detached
589 if (from_inv.type == InventoryLocation::DETACHED) {
590 ItemStack src_item = list_from->getItem(from_i);
591 src_item.count = take_count;
592 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
593 from_inv.name, from_list, from_i, src_item, player);
596 // Source is nodemeta
597 if (from_inv.type == InventoryLocation::NODEMETA) {
598 ItemStack src_item = list_from->getItem(from_i);
599 src_item.count = take_count;
600 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
601 from_inv.p, from_list, from_i, src_item, player);
604 if (src_can_take_count != -1 && src_can_take_count < take_count)
605 take_count = src_can_take_count;
607 int actually_dropped_count = 0;
609 ItemStack src_item = list_from->getItem(from_i);
612 ItemStack item1 = list_from->getItem(from_i);
613 item1.count = take_count;
614 if (PLAYER_TO_SA(player)->item_OnDrop(item1, player,
615 player->getBasePosition() + v3f(0,1,0))) {
616 actually_dropped_count = take_count - item1.count;
618 if (actually_dropped_count == 0) {
619 infostream<<"Actually dropped no items"<<std::endl;
623 // If source isn't infinite
624 if (src_can_take_count != -1) {
625 // Take item from source list
626 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
628 if (item2.count != actually_dropped_count)
629 errorstream<<"Could not take dropped count of items"<<std::endl;
631 mgr->setInventoryModified(from_inv, false);
635 infostream<<"IDropAction::apply(): dropped "
636 <<" from inv=\""<<from_inv.dump()<<"\""
637 <<" list=\""<<from_list<<"\""
641 src_item.count = actually_dropped_count;
644 Report drop to endpoints
647 // Source is detached
648 if (from_inv.type == InventoryLocation::DETACHED) {
649 PLAYER_TO_SA(player)->detached_inventory_OnTake(
650 from_inv.name, from_list, from_i, src_item, player);
653 // Source is nodemeta
654 if (from_inv.type == InventoryLocation::NODEMETA) {
655 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
656 from_inv.p, from_list, from_i, src_item, player);
660 Record rollback information
662 if (!ignore_src_rollback && gamedef->rollback()) {
663 IRollbackManager *rollback = gamedef->rollback();
665 // If source is not infinite, record item take
666 if (src_can_take_count != -1) {
667 RollbackAction action;
670 std::ostringstream os(std::ios::binary);
671 from_inv.serialize(os);
674 action.setModifyInventoryStack(loc, from_list, from_i,
676 rollback->reportAction(action);
681 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
683 // Optional InventoryAction operation that is run on the client
684 // to make lag less apparent.
686 Inventory *inv_from = mgr->getInventory(from_inv);
690 InventoryLocation current_player;
691 current_player.setCurrentPlayer();
692 Inventory *inv_player = mgr->getInventory(current_player);
693 if (inv_from != inv_player)
696 InventoryList *list_from = inv_from->getList(from_list);
701 list_from->changeItem(from_i, ItemStack());
703 list_from->takeItem(from_i, count);
705 mgr->setInventoryModified(from_inv);
712 ICraftAction::ICraftAction(std::istream &is)
716 std::getline(is, ts, ' ');
719 std::getline(is, ts, ' ');
720 craft_inv.deSerialize(ts);
723 void ICraftAction::apply(InventoryManager *mgr,
724 ServerActiveObject *player, IGameDef *gamedef)
726 Inventory *inv_craft = mgr->getInventory(craft_inv);
729 infostream << "ICraftAction::apply(): FAIL: inventory not found: "
730 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
734 InventoryList *list_craft = inv_craft->getList("craft");
735 InventoryList *list_craftresult = inv_craft->getList("craftresult");
736 InventoryList *list_main = inv_craft->getList("main");
739 If a list doesn't exist or the source item doesn't exist
742 infostream << "ICraftAction::apply(): FAIL: craft list not found: "
743 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
746 if (!list_craftresult) {
747 infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
748 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
751 if (list_craftresult->getSize() < 1) {
752 infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
753 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
758 ItemStack craftresultitem;
759 int count_remaining = count;
760 std::vector<ItemStack> output_replacements;
761 getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
762 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
763 bool found = !crafted.empty();
765 while (found && list_craftresult->itemFits(0, crafted)) {
766 InventoryList saved_craft_list = *list_craft;
768 std::vector<ItemStack> temp;
769 // Decrement input and add crafting output
770 getCraftingResult(inv_craft, crafted, temp, true, gamedef);
771 PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
772 list_craftresult->addItem(0, crafted);
773 mgr->setInventoryModified(craft_inv);
775 // Add the new replacements to the list
776 IItemDefManager *itemdef = gamedef->getItemDefManager();
777 for (std::vector<ItemStack>::iterator it = temp.begin();
778 it != temp.end(); ++it) {
779 for (std::vector<ItemStack>::iterator jt = output_replacements.begin();
780 jt != output_replacements.end(); ++jt) {
781 if (it->name == jt->name) {
782 *it = jt->addItem(*it, itemdef);
787 output_replacements.push_back(*it);
790 actionstream << player->getDescription()
792 << crafted.getItemString()
796 if (count_remaining == 1)
798 else 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 (std::vector<ItemStack>::iterator it = output_replacements.begin();
810 it != output_replacements.end(); ++it) {
812 *it = list_main->addItem(*it);
815 u16 count = it->count;
817 PLAYER_TO_SA(player)->item_OnDrop(*it, player,
818 player->getBasePosition() + v3f(0,1,0));
819 if (count >= it->count) {
820 errorstream << "Couldn't drop replacement stack " <<
821 it->getItemString() << " because drop loop didn't "
822 "decrease count." << std::endl;
826 } while (!it->empty());
829 infostream<<"ICraftAction::apply(): crafted "
830 <<" craft_inv=\""<<craft_inv.dump()<<"\""
834 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
836 // Optional InventoryAction operation that is run on the client
837 // to make lag less apparent.
842 bool getCraftingResult(Inventory *inv, ItemStack &result,
843 std::vector<ItemStack> &output_replacements,
844 bool decrementInput, IGameDef *gamedef)
846 DSTACK(FUNCTION_NAME);
850 // Get the InventoryList in which we will operate
851 InventoryList *clist = inv->getList("craft");
855 // Mangle crafting grid to an another format
857 ci.method = CRAFT_METHOD_NORMAL;
858 ci.width = clist->getWidth() ? clist->getWidth() : 3;
859 for (u16 i=0; i < clist->getSize(); i++)
860 ci.items.push_back(clist->getItem(i));
862 // Find out what is crafted and add it to result item slot
864 bool found = gamedef->getCraftDefManager()->getCraftResult(
865 ci, co, output_replacements, decrementInput, gamedef);
867 result.deSerialize(co.item, gamedef->getItemDefManager());
869 if (found && decrementInput) {
870 // CraftInput has been changed, apply changes in clist
871 for (u16 i=0; i < clist->getSize(); i++) {
872 clist->changeItem(i, ci.items[i]);