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 (from_i < 0 || list_from->getSize() <= (u32) from_i) {
326 infostream << "IMoveAction::apply(): FAIL: source index out of bounds: "
327 << "size of from_list=\"" << list_from->getSize() << "\""
328 << ", from_index=\"" << from_i << "\"" << std::endl;
332 if (to_i < 0 || list_to->getSize() <= (u32) to_i) {
333 infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
334 << "size of to_list=\"" << list_to->getSize() << "\""
335 << ", to_index=\"" << to_i << "\"" << std::endl;
340 Do not handle rollback if both inventories are that of the same player
342 bool ignore_rollback = (
343 from_inv.type == InventoryLocation::PLAYER &&
347 Collect information of endpoints
350 ItemStack src_item = list_from->getItem(from_i);
351 if (count > 0 && count < src_item.count)
352 src_item.count = count;
353 if (src_item.empty())
356 int src_can_take_count = 0xffff;
357 int dst_can_put_count = 0xffff;
359 // this is needed for swapping items inside one inventory to work
361 bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
362 && restitem.count == src_item.count
363 && !caused_by_move_somewhere;
364 move_count = src_item.count - restitem.count;
366 // Shift-click: Cannot fill this stack, proceed with next slot
367 if (caused_by_move_somewhere && move_count == 0) {
372 // Swap will affect the entire stack if it can performed.
373 src_item = list_from->getItem(from_i);
374 count = src_item.count;
377 if (from_inv == to_inv) {
378 // Move action within the same inventory
379 src_can_take_count = allowMove(src_item.count, player);
381 bool swap_expected = allow_swap;
382 allow_swap = allow_swap
383 && (src_can_take_count == -1 || src_can_take_count >= src_item.count);
385 int try_put_count = list_to->getItem(to_i).count;
387 dst_can_put_count = allowMove(try_put_count, player);
388 allow_swap = allow_swap
389 && (dst_can_put_count == -1 || dst_can_put_count >= try_put_count);
392 dst_can_put_count = src_can_take_count;
394 if (swap_expected != allow_swap)
395 src_can_take_count = dst_can_put_count = 0;
397 // Take from one inventory, put into another
398 int src_item_count = src_item.count;
399 if (caused_by_move_somewhere)
400 // When moving somewhere: temporarily use the actual movable stack
401 // size to ensure correct callback execution.
402 src_item.count = move_count;
403 dst_can_put_count = allowPut(src_item, player);
404 src_can_take_count = allowTake(src_item, player);
405 if (caused_by_move_somewhere)
406 // Reset source item count
407 src_item.count = src_item_count;
408 bool swap_expected = allow_swap;
409 allow_swap = allow_swap
410 && (src_can_take_count == -1 || src_can_take_count >= src_item.count)
411 && (dst_can_put_count == -1 || dst_can_put_count >= src_item.count);
412 // A swap is expected, which means that we have to
413 // run the "allow" callbacks a second time with swapped inventories
415 ItemStack dst_item = list_to->getItem(to_i);
418 int src_can_take = allowPut(dst_item, player);
419 int dst_can_put = allowTake(dst_item, player);
420 allow_swap = allow_swap
421 && (src_can_take == -1 || src_can_take >= dst_item.count)
422 && (dst_can_put == -1 || dst_can_put >= dst_item.count);
425 if (swap_expected != allow_swap)
426 src_can_take_count = dst_can_put_count = 0;
429 int old_count = count;
431 /* Modify count according to collected data */
432 count = src_item.count;
433 if (src_can_take_count != -1 && count > src_can_take_count)
434 count = src_can_take_count;
435 if (dst_can_put_count != -1 && count > dst_can_put_count)
436 count = dst_can_put_count;
438 /* Limit according to source item count */
439 if (count > list_from->getItem(from_i).count)
440 count = list_from->getItem(from_i).count;
442 /* If no items will be moved, don't go further */
444 if (caused_by_move_somewhere)
445 // Set move count to zero, as no items have been moved
448 // Undo client prediction. See 'clientApply'
449 if (from_inv.type == InventoryLocation::PLAYER)
450 list_from->setModified();
452 if (to_inv.type == InventoryLocation::PLAYER)
453 list_to->setModified();
455 infostream<<"IMoveAction::apply(): move was completely disallowed:"
456 <<" count="<<old_count
457 <<" from inv=\""<<from_inv.dump()<<"\""
458 <<" list=\""<<from_list<<"\""
460 <<" to inv=\""<<to_inv.dump()<<"\""
461 <<" list=\""<<to_list<<"\""
468 src_item = list_from->getItem(from_i);
469 src_item.count = count;
470 ItemStack from_stack_was = list_from->getItem(from_i);
471 ItemStack to_stack_was = list_to->getItem(to_i);
476 If something is wrong (source item is empty, destination is the
477 same as source), nothing happens
479 bool did_swap = false;
480 move_count = list_from->moveItem(from_i,
481 list_to, to_i, count, allow_swap, &did_swap);
482 if (caused_by_move_somewhere)
484 assert(allow_swap == did_swap);
486 // If source is infinite, reset it's stack
487 if (src_can_take_count == -1) {
488 // For the caused_by_move_somewhere == true case we didn't force-put the item,
489 // which guarantees there is no leftover, and code below would duplicate the
490 // (not replaced) to_stack_was item.
491 if (!caused_by_move_somewhere) {
492 // If destination stack is of different type and there are leftover
493 // items, attempt to put the leftover items to a different place in the
494 // destination inventory.
495 // The client-side GUI will try to guess if this happens.
496 if (from_stack_was.name != to_stack_was.name) {
497 for (u32 i = 0; i < list_to->getSize(); i++) {
498 if (list_to->getItem(i).empty()) {
499 list_to->changeItem(i, to_stack_was);
505 if (move_count > 0 || did_swap) {
506 list_from->deleteItem(from_i);
507 list_from->addItem(from_i, from_stack_was);
510 // If destination is infinite, reset it's stack and take count from source
511 if (dst_can_put_count == -1) {
512 list_to->deleteItem(to_i);
513 list_to->addItem(to_i, to_stack_was);
514 list_from->deleteItem(from_i);
515 list_from->addItem(from_i, from_stack_was);
516 list_from->takeItem(from_i, count);
519 infostream << "IMoveAction::apply(): moved"
520 << " msom=" << move_somewhere
521 << " caused=" << caused_by_move_somewhere
522 << " count=" << count
523 << " from inv=\"" << from_inv.dump() << "\""
524 << " list=\"" << from_list << "\""
526 << " to inv=\"" << to_inv.dump() << "\""
527 << " list=\"" << to_list << "\""
531 // If we are inside the move somewhere loop, we don't need to report
532 // anything if nothing happened
533 if (caused_by_move_somewhere && move_count == 0)
537 Record rollback information
539 if (!ignore_rollback && gamedef->rollback()) {
540 IRollbackManager *rollback = gamedef->rollback();
542 // If source is not infinite, record item take
543 if (src_can_take_count != -1) {
544 RollbackAction action;
547 std::ostringstream os(std::ios::binary);
548 from_inv.serialize(os);
551 action.setModifyInventoryStack(loc, from_list, from_i, false,
553 rollback->reportAction(action);
555 // If destination is not infinite, record item put
556 if (dst_can_put_count != -1) {
557 RollbackAction action;
560 std::ostringstream os(std::ios::binary);
561 to_inv.serialize(os);
564 action.setModifyInventoryStack(loc, to_list, to_i, true,
566 rollback->reportAction(action);
571 Report move to endpoints
574 // Source = destination => move
575 if (from_inv == to_inv) {
576 onMove(count, player);
578 // Item is now placed in source list
579 src_item = list_from->getItem(from_i);
581 onMove(src_item.count, player);
584 mgr->setInventoryModified(from_inv);
586 int src_item_count = src_item.count;
587 if (caused_by_move_somewhere)
588 // When moving somewhere: temporarily use the actual movable stack
589 // size to ensure correct callback execution.
590 src_item.count = move_count;
591 onPutAndOnTake(src_item, player);
592 if (caused_by_move_somewhere)
593 // Reset source item count
594 src_item.count = src_item_count;
596 // Item is now placed in source list
597 src_item = list_from->getItem(from_i);
599 onPutAndOnTake(src_item, player);
602 mgr->setInventoryModified(to_inv);
603 mgr->setInventoryModified(from_inv);
607 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
609 // Optional InventoryAction operation that is run on the client
610 // to make lag less apparent.
612 Inventory *inv_from = mgr->getInventory(from_inv);
613 Inventory *inv_to = mgr->getInventory(to_inv);
614 if (!inv_from || !inv_to)
617 InventoryLocation current_player;
618 current_player.setCurrentPlayer();
619 Inventory *inv_player = mgr->getInventory(current_player);
620 if (inv_from != inv_player || inv_to != inv_player)
623 InventoryList *list_from = inv_from->getList(from_list);
624 InventoryList *list_to = inv_to->getList(to_list);
625 if (!list_from || !list_to)
629 list_from->moveItem(from_i, list_to, to_i, count);
631 list_from->moveItemSomewhere(from_i, list_to, count);
633 mgr->setInventoryModified(from_inv);
634 if (inv_from != inv_to)
635 mgr->setInventoryModified(to_inv);
642 IDropAction::IDropAction(std::istream &is)
646 std::getline(is, ts, ' ');
649 std::getline(is, ts, ' ');
650 from_inv.deSerialize(ts);
652 std::getline(is, from_list, ' ');
654 std::getline(is, ts, ' ');
658 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
660 Inventory *inv_from = mgr->getInventory(from_inv);
663 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
664 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
668 InventoryList *list_from = inv_from->getList(from_list);
671 If a list doesn't exist or the source item doesn't exist
674 infostream<<"IDropAction::apply(): FAIL: source list not found: "
675 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
678 if (list_from->getItem(from_i).empty()) {
679 infostream<<"IDropAction::apply(): FAIL: source item not found: "
680 <<"from_inv=\""<<from_inv.dump()<<"\""
681 <<", from_list=\""<<from_list<<"\""
682 <<" from_i="<<from_i<<std::endl;
687 Do not handle rollback if inventory is player's
689 bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
692 Collect information of endpoints
695 int take_count = list_from->getItem(from_i).count;
696 if (count != 0 && count < take_count)
698 int src_can_take_count = take_count;
700 ItemStack src_item = list_from->getItem(from_i);
701 src_item.count = take_count;
703 // Run callbacks depending on source inventory
704 switch (from_inv.type) {
705 case InventoryLocation::DETACHED:
706 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
707 *this, src_item, player);
709 case InventoryLocation::NODEMETA:
710 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
711 *this, src_item, player);
713 case InventoryLocation::PLAYER:
714 src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake(
715 *this, src_item, player);
721 if (src_can_take_count != -1 && src_can_take_count < take_count)
722 take_count = src_can_take_count;
724 // Update item due executed callbacks
725 src_item = list_from->getItem(from_i);
728 ItemStack item1 = list_from->getItem(from_i);
729 item1.count = take_count;
730 if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
731 player->getBasePosition())) {
732 int actually_dropped_count = take_count - item1.count;
734 if (actually_dropped_count == 0) {
735 infostream<<"Actually dropped no items"<<std::endl;
737 // Revert client prediction. See 'clientApply'
738 if (from_inv.type == InventoryLocation::PLAYER)
739 list_from->setModified();
743 // If source isn't infinite
744 if (src_can_take_count != -1) {
745 // Take item from source list
746 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
748 if (item2.count != actually_dropped_count)
749 errorstream<<"Could not take dropped count of items"<<std::endl;
752 src_item.count = actually_dropped_count;
753 mgr->setInventoryModified(from_inv);
756 infostream<<"IDropAction::apply(): dropped "
757 <<" from inv=\""<<from_inv.dump()<<"\""
758 <<" list=\""<<from_list<<"\""
764 Report drop to endpoints
767 switch (from_inv.type) {
768 case InventoryLocation::DETACHED:
769 PLAYER_TO_SA(player)->detached_inventory_OnTake(
770 *this, src_item, player);
772 case InventoryLocation::NODEMETA:
773 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
774 *this, src_item, player);
776 case InventoryLocation::PLAYER:
777 PLAYER_TO_SA(player)->player_inventory_OnTake(
778 *this, src_item, player);
785 Record rollback information
787 if (!ignore_src_rollback && gamedef->rollback()) {
788 IRollbackManager *rollback = gamedef->rollback();
790 // If source is not infinite, record item take
791 if (src_can_take_count != -1) {
792 RollbackAction action;
795 std::ostringstream os(std::ios::binary);
796 from_inv.serialize(os);
799 action.setModifyInventoryStack(loc, from_list, from_i,
801 rollback->reportAction(action);
806 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
808 // Optional InventoryAction operation that is run on the client
809 // to make lag less apparent.
811 Inventory *inv_from = mgr->getInventory(from_inv);
815 InventoryLocation current_player;
816 current_player.setCurrentPlayer();
817 Inventory *inv_player = mgr->getInventory(current_player);
818 if (inv_from != inv_player)
821 InventoryList *list_from = inv_from->getList(from_list);
826 list_from->changeItem(from_i, ItemStack());
828 list_from->takeItem(from_i, count);
830 mgr->setInventoryModified(from_inv);
837 ICraftAction::ICraftAction(std::istream &is)
841 std::getline(is, ts, ' ');
844 std::getline(is, ts, ' ');
845 craft_inv.deSerialize(ts);
848 void ICraftAction::apply(InventoryManager *mgr,
849 ServerActiveObject *player, IGameDef *gamedef)
851 Inventory *inv_craft = mgr->getInventory(craft_inv);
854 infostream << "ICraftAction::apply(): FAIL: inventory not found: "
855 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
859 InventoryList *list_craft = inv_craft->getList("craft");
860 InventoryList *list_craftresult = inv_craft->getList("craftresult");
861 InventoryList *list_main = inv_craft->getList("main");
864 If a list doesn't exist or the source item doesn't exist
867 infostream << "ICraftAction::apply(): FAIL: craft list not found: "
868 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
871 if (!list_craftresult) {
872 infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
873 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
876 if (list_craftresult->getSize() < 1) {
877 infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
878 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
883 ItemStack craftresultitem;
884 int count_remaining = count;
885 std::vector<ItemStack> output_replacements;
886 getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
887 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
888 bool found = !crafted.empty();
890 while (found && list_craftresult->itemFits(0, crafted)) {
891 InventoryList saved_craft_list = *list_craft;
893 std::vector<ItemStack> temp;
894 // Decrement input and add crafting output
895 getCraftingResult(inv_craft, crafted, temp, true, gamedef);
896 PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
897 list_craftresult->addItem(0, crafted);
898 mgr->setInventoryModified(craft_inv);
900 // Add the new replacements to the list
901 IItemDefManager *itemdef = gamedef->getItemDefManager();
902 for (auto &itemstack : temp) {
903 for (auto &output_replacement : output_replacements) {
904 if (itemstack.name == output_replacement.name) {
905 itemstack = output_replacement.addItem(itemstack, itemdef);
906 if (itemstack.empty())
910 output_replacements.push_back(itemstack);
913 actionstream << player->getDescription()
915 << crafted.getItemString()
919 if (count_remaining == 1)
922 if (count_remaining > 1)
925 // Get next crafting result
926 getCraftingResult(inv_craft, crafted, temp, false, gamedef);
927 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
928 found = !crafted.empty();
931 // Put the replacements in the inventory or drop them on the floor, if
932 // the inventory is full
933 for (auto &output_replacement : output_replacements) {
935 output_replacement = list_main->addItem(output_replacement);
936 if (output_replacement.empty())
938 u16 count = output_replacement.count;
940 PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player,
941 player->getBasePosition());
942 if (count >= output_replacement.count) {
943 errorstream << "Couldn't drop replacement stack " <<
944 output_replacement.getItemString() << " because drop loop didn't "
945 "decrease count." << std::endl;
949 } while (!output_replacement.empty());
952 infostream<<"ICraftAction::apply(): crafted "
953 <<" craft_inv=\""<<craft_inv.dump()<<"\""
957 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
959 // Optional InventoryAction operation that is run on the client
960 // to make lag less apparent.
965 bool getCraftingResult(Inventory *inv, ItemStack &result,
966 std::vector<ItemStack> &output_replacements,
967 bool decrementInput, IGameDef *gamedef)
971 // Get the InventoryList in which we will operate
972 InventoryList *clist = inv->getList("craft");
976 // Mangle crafting grid to an another format
978 ci.method = CRAFT_METHOD_NORMAL;
979 ci.width = clist->getWidth() ? clist->getWidth() : 3;
980 for (u16 i=0; i < clist->getSize(); i++)
981 ci.items.push_back(clist->getItem(i));
983 // Find out what is crafted and add it to result item slot
985 bool found = gamedef->getCraftDefManager()->getCraftResult(
986 ci, co, output_replacements, decrementInput, gamedef);
988 result.deSerialize(co.item, gamedef->getItemDefManager());
990 if (found && decrementInput) {
991 // CraftInput has been changed, apply changes in clist
992 for (u16 i=0; i < clist->getSize(); i++) {
993 clist->changeItem(i, ci.items[i]);