1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "inventorymanager.h"
21 #include "debug.h"
22 #include "log.h"
23 #include "serverenvironment.h"
24 #include "scripting_server.h"
25 #include "server/serveractiveobject.h"
26 #include "settings.h"
27 #include "craftdef.h"
28 #include "rollback_interface.h"
29 #include "util/strfnd.h"
30 #include "util/basic_macros.h"
31
32 #define PLAYER_TO_SA(p) p->getEnv()->getScriptIface()
33
34 /*
35 InventoryLocation
36 */
37
dump() const38 std::string InventoryLocation::dump() const
39 {
40 std::ostringstream os(std::ios::binary);
41 serialize(os);
42 return os.str();
43 }
44
serialize(std::ostream & os) const45 void InventoryLocation::serialize(std::ostream &os) const
46 {
47 switch (type) {
48 case InventoryLocation::UNDEFINED:
49 os<<"undefined";
50 break;
51 case InventoryLocation::CURRENT_PLAYER:
52 os<<"current_player";
53 break;
54 case InventoryLocation::PLAYER:
55 os<<"player:"<<name;
56 break;
57 case InventoryLocation::NODEMETA:
58 os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
59 break;
60 case InventoryLocation::DETACHED:
61 os<<"detached:"<<name;
62 break;
63 default:
64 FATAL_ERROR("Unhandled inventory location type");
65 }
66 }
67
deSerialize(std::istream & is)68 void InventoryLocation::deSerialize(std::istream &is)
69 {
70 std::string tname;
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;
81 std::string pos;
82 std::getline(is, pos, '\n');
83 Strfnd fn(pos);
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');
90 } else {
91 infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
92 throw SerializationError("Unknown InventoryLocation type");
93 }
94 }
95
deSerialize(const std::string & s)96 void InventoryLocation::deSerialize(const std::string &s)
97 {
98 std::istringstream is(s, std::ios::binary);
99 deSerialize(is);
100 }
101
102 /*
103 InventoryAction
104 */
105
deSerialize(std::istream & is)106 InventoryAction *InventoryAction::deSerialize(std::istream &is)
107 {
108 std::string type;
109 std::getline(is, type, ' ');
110
111 InventoryAction *a = nullptr;
112
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);
121 }
122
123 return a;
124 }
125
126 /*
127 IMoveAction
128 */
129
IMoveAction(std::istream & is,bool somewhere)130 IMoveAction::IMoveAction(std::istream &is, bool somewhere) :
131 move_somewhere(somewhere)
132 {
133 std::string ts;
134
135 std::getline(is, ts, ' ');
136 count = stoi(ts);
137
138 std::getline(is, ts, ' ');
139 from_inv.deSerialize(ts);
140
141 std::getline(is, from_list, ' ');
142
143 std::getline(is, ts, ' ');
144 from_i = stoi(ts);
145
146 std::getline(is, ts, ' ');
147 to_inv.deSerialize(ts);
148
149 std::getline(is, to_list, ' ');
150
151 if (!somewhere) {
152 std::getline(is, ts, ' ');
153 to_i = stoi(ts);
154 }
155 }
156
swapDirections()157 void IMoveAction::swapDirections()
158 {
159 std::swap(from_inv, to_inv);
160 std::swap(from_list, to_list);
161 std::swap(from_i, to_i);
162 }
163
onPutAndOnTake(const ItemStack & src_item,ServerActiveObject * player) const164 void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const
165 {
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);
173 else
174 assert(false);
175
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);
182 else
183 assert(false);
184 }
185
onMove(int count,ServerActiveObject * player) const186 void IMoveAction::onMove(int count, ServerActiveObject *player) const
187 {
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);
195 else
196 assert(false);
197 }
198
allowPut(const ItemStack & dst_item,ServerActiveObject * player) const199 int IMoveAction::allowPut(const ItemStack &dst_item, ServerActiveObject *player) const
200 {
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);
209 else
210 assert(false);
211 return dst_can_put_count;
212 }
213
allowTake(const ItemStack & src_item,ServerActiveObject * player) const214 int IMoveAction::allowTake(const ItemStack &src_item, ServerActiveObject *player) const
215 {
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);
224 else
225 assert(false);
226 return src_can_take_count;
227 }
228
allowMove(int try_take_count,ServerActiveObject * player) const229 int IMoveAction::allowMove(int try_take_count, ServerActiveObject *player) const
230 {
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);
239 else
240 assert(false);
241 return src_can_take_count;
242 }
243
apply(InventoryManager * mgr,ServerActiveObject * player,IGameDef * gamedef)244 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
245 {
246 Inventory *inv_from = mgr->getInventory(from_inv);
247 Inventory *inv_to = mgr->getInventory(to_inv);
248
249 if (!inv_from) {
250 infostream << "IMoveAction::apply(): FAIL: source inventory not found: "
251 << "from_inv=\""<<from_inv.dump() << "\""
252 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
253 return;
254 }
255 if (!inv_to) {
256 infostream << "IMoveAction::apply(): FAIL: destination inventory not found: "
257 << "from_inv=\"" << from_inv.dump() << "\""
258 << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
259 return;
260 }
261
262 InventoryList *list_from = inv_from->getList(from_list);
263 InventoryList *list_to = inv_to->getList(to_list);
264
265 /*
266 If a list doesn't exist or the source item doesn't exist
267 */
268 if (!list_from) {
269 infostream << "IMoveAction::apply(): FAIL: source list not found: "
270 << "from_inv=\"" << from_inv.dump() << "\""
271 << ", from_list=\"" << from_list << "\"" << std::endl;
272 return;
273 }
274 if (!list_to) {
275 infostream << "IMoveAction::apply(): FAIL: destination list not found: "
276 << "to_inv=\""<<to_inv.dump() << "\""
277 << ", to_list=\"" << to_list << "\"" << std::endl;
278 return;
279 }
280
281 if (move_somewhere) {
282 s16 old_to_i = to_i;
283 u16 old_count = count;
284 caused_by_move_somewhere = true;
285 move_somewhere = false;
286
287 infostream << "IMoveAction::apply(): moving item somewhere"
288 << " msom=" << move_somewhere
289 << " count=" << count
290 << " from inv=\"" << from_inv.dump() << "\""
291 << " list=\"" << from_list << "\""
292 << " i=" << from_i
293 << " to inv=\"" << to_inv.dump() << "\""
294 << " list=\"" << to_list << "\""
295 << std::endl;
296
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()) {
302 to_i = dest_i;
303 apply(mgr, player, gamedef);
304 assert(move_count <= count);
305 count -= move_count;
306 }
307 }
308
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()) {
312 to_i = dest_i;
313 apply(mgr, player, gamedef);
314 count -= move_count;
315 }
316 }
317
318 to_i = old_to_i;
319 count = old_count;
320 caused_by_move_somewhere = false;
321 move_somewhere = true;
322 return;
323 }
324
325 if ((u16)to_i > list_to->getSize()) {
326 infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
327 << "to_i=" << to_i
328 << ", size=" << list_to->getSize() << std::endl;
329 return;
330 }
331 /*
332 Do not handle rollback if both inventories are that of the same player
333 */
334 bool ignore_rollback = (
335 from_inv.type == InventoryLocation::PLAYER &&
336 from_inv == to_inv);
337
338 /*
339 Collect information of endpoints
340 */
341
342 ItemStack src_item = list_from->getItem(from_i);
343 if (count > 0 && count < src_item.count)
344 src_item.count = count;
345 if (src_item.empty())
346 return;
347
348 int src_can_take_count = 0xffff;
349 int dst_can_put_count = 0xffff;
350
351 // this is needed for swapping items inside one inventory to work
352 ItemStack restitem;
353 bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
354 && restitem.count == src_item.count
355 && !caused_by_move_somewhere;
356 move_count = src_item.count - restitem.count;
357
358 // Shift-click: Cannot fill this stack, proceed with next slot
359 if (caused_by_move_somewhere && move_count == 0) {
360 return;
361 }
362
363 if (allow_swap) {
364 // Swap will affect the entire stack if it can performed.
365 src_item = list_from->getItem(from_i);
366 count = src_item.count;
367 }
368
369 if (from_inv == to_inv) {
370 // Move action within the same inventory
371 src_can_take_count = allowMove(src_item.count, player);
372
373 bool swap_expected = allow_swap;
374 allow_swap = allow_swap
375 && (src_can_take_count == -1 || src_can_take_count >= src_item.count);
376 if (allow_swap) {
377 int try_put_count = list_to->getItem(to_i).count;
378 swapDirections();
379 dst_can_put_count = allowMove(try_put_count, player);
380 allow_swap = allow_swap
381 && (dst_can_put_count == -1 || dst_can_put_count >= try_put_count);
382 swapDirections();
383 } else {
384 dst_can_put_count = src_can_take_count;
385 }
386 if (swap_expected != allow_swap)
387 src_can_take_count = dst_can_put_count = 0;
388 } else {
389 // Take from one inventory, put into another
390 int src_item_count = src_item.count;
391 if (caused_by_move_somewhere)
392 // When moving somewhere: temporarily use the actual movable stack
393 // size to ensure correct callback execution.
394 src_item.count = move_count;
395 dst_can_put_count = allowPut(src_item, player);
396 src_can_take_count = allowTake(src_item, player);
397 if (caused_by_move_somewhere)
398 // Reset source item count
399 src_item.count = src_item_count;
400 bool swap_expected = allow_swap;
401 allow_swap = allow_swap
402 && (src_can_take_count == -1 || src_can_take_count >= src_item.count)
403 && (dst_can_put_count == -1 || dst_can_put_count >= src_item.count);
404 // A swap is expected, which means that we have to
405 // run the "allow" callbacks a second time with swapped inventories
406 if (allow_swap) {
407 ItemStack dst_item = list_to->getItem(to_i);
408 swapDirections();
409
410 int src_can_take = allowPut(dst_item, player);
411 int dst_can_put = allowTake(dst_item, player);
412 allow_swap = allow_swap
413 && (src_can_take == -1 || src_can_take >= dst_item.count)
414 && (dst_can_put == -1 || dst_can_put >= dst_item.count);
415 swapDirections();
416 }
417 if (swap_expected != allow_swap)
418 src_can_take_count = dst_can_put_count = 0;
419 }
420
421 int old_count = count;
422
423 /* Modify count according to collected data */
424 count = src_item.count;
425 if (src_can_take_count != -1 && count > src_can_take_count)
426 count = src_can_take_count;
427 if (dst_can_put_count != -1 && count > dst_can_put_count)
428 count = dst_can_put_count;
429
430 /* Limit according to source item count */
431 if (count > list_from->getItem(from_i).count)
432 count = list_from->getItem(from_i).count;
433
434 /* If no items will be moved, don't go further */
435 if (count == 0) {
436 if (caused_by_move_somewhere)
437 // Set move count to zero, as no items have been moved
438 move_count = 0;
439
440 // Undo client prediction. See 'clientApply'
441 if (from_inv.type == InventoryLocation::PLAYER)
442 list_from->setModified();
443
444 if (to_inv.type == InventoryLocation::PLAYER)
445 list_to->setModified();
446
447 infostream<<"IMoveAction::apply(): move was completely disallowed:"
448 <<" count="<<old_count
449 <<" from inv=\""<<from_inv.dump()<<"\""
450 <<" list=\""<<from_list<<"\""
451 <<" i="<<from_i
452 <<" to inv=\""<<to_inv.dump()<<"\""
453 <<" list=\""<<to_list<<"\""
454 <<" i="<<to_i
455 <<std::endl;
456
457 return;
458 }
459
460 src_item = list_from->getItem(from_i);
461 src_item.count = count;
462 ItemStack from_stack_was = list_from->getItem(from_i);
463 ItemStack to_stack_was = list_to->getItem(to_i);
464
465 /*
466 Perform actual move
467
468 If something is wrong (source item is empty, destination is the
469 same as source), nothing happens
470 */
471 bool did_swap = false;
472 move_count = list_from->moveItem(from_i,
473 list_to, to_i, count, allow_swap, &did_swap);
474 if (caused_by_move_somewhere)
475 count = old_count;
476 assert(allow_swap == did_swap);
477
478 // If source is infinite, reset it's stack
479 if (src_can_take_count == -1) {
480 // For the caused_by_move_somewhere == true case we didn't force-put the item,
481 // which guarantees there is no leftover, and code below would duplicate the
482 // (not replaced) to_stack_was item.
483 if (!caused_by_move_somewhere) {
484 // If destination stack is of different type and there are leftover
485 // items, attempt to put the leftover items to a different place in the
486 // destination inventory.
487 // The client-side GUI will try to guess if this happens.
488 if (from_stack_was.name != to_stack_was.name) {
489 for (u32 i = 0; i < list_to->getSize(); i++) {
490 if (list_to->getItem(i).empty()) {
491 list_to->changeItem(i, to_stack_was);
492 break;
493 }
494 }
495 }
496 }
497 if (move_count > 0 || did_swap) {
498 list_from->deleteItem(from_i);
499 list_from->addItem(from_i, from_stack_was);
500 }
501 }
502 // If destination is infinite, reset it's stack and take count from source
503 if (dst_can_put_count == -1) {
504 list_to->deleteItem(to_i);
505 list_to->addItem(to_i, to_stack_was);
506 list_from->deleteItem(from_i);
507 list_from->addItem(from_i, from_stack_was);
508 list_from->takeItem(from_i, count);
509 }
510
511 infostream << "IMoveAction::apply(): moved"
512 << " msom=" << move_somewhere
513 << " caused=" << caused_by_move_somewhere
514 << " count=" << count
515 << " from inv=\"" << from_inv.dump() << "\""
516 << " list=\"" << from_list << "\""
517 << " i=" << from_i
518 << " to inv=\"" << to_inv.dump() << "\""
519 << " list=\"" << to_list << "\""
520 << " i=" << to_i
521 << std::endl;
522
523 // If we are inside the move somewhere loop, we don't need to report
524 // anything if nothing happened
525 if (caused_by_move_somewhere && move_count == 0)
526 return;
527
528 /*
529 Record rollback information
530 */
531 if (!ignore_rollback && gamedef->rollback()) {
532 IRollbackManager *rollback = gamedef->rollback();
533
534 // If source is not infinite, record item take
535 if (src_can_take_count != -1) {
536 RollbackAction action;
537 std::string loc;
538 {
539 std::ostringstream os(std::ios::binary);
540 from_inv.serialize(os);
541 loc = os.str();
542 }
543 action.setModifyInventoryStack(loc, from_list, from_i, false,
544 src_item);
545 rollback->reportAction(action);
546 }
547 // If destination is not infinite, record item put
548 if (dst_can_put_count != -1) {
549 RollbackAction action;
550 std::string loc;
551 {
552 std::ostringstream os(std::ios::binary);
553 to_inv.serialize(os);
554 loc = os.str();
555 }
556 action.setModifyInventoryStack(loc, to_list, to_i, true,
557 src_item);
558 rollback->reportAction(action);
559 }
560 }
561
562 /*
563 Report move to endpoints
564 */
565
566 // Source = destination => move
567 if (from_inv == to_inv) {
568 onMove(count, player);
569 if (did_swap) {
570 // Item is now placed in source list
571 src_item = list_from->getItem(from_i);
572 swapDirections();
573 onMove(src_item.count, player);
574 swapDirections();
575 }
576 mgr->setInventoryModified(from_inv);
577 } else {
578 int src_item_count = src_item.count;
579 if (caused_by_move_somewhere)
580 // When moving somewhere: temporarily use the actual movable stack
581 // size to ensure correct callback execution.
582 src_item.count = move_count;
583 onPutAndOnTake(src_item, player);
584 if (caused_by_move_somewhere)
585 // Reset source item count
586 src_item.count = src_item_count;
587 if (did_swap) {
588 // Item is now placed in source list
589 src_item = list_from->getItem(from_i);
590 swapDirections();
591 onPutAndOnTake(src_item, player);
592 swapDirections();
593 }
594 mgr->setInventoryModified(to_inv);
595 mgr->setInventoryModified(from_inv);
596 }
597 }
598
clientApply(InventoryManager * mgr,IGameDef * gamedef)599 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
600 {
601 // Optional InventoryAction operation that is run on the client
602 // to make lag less apparent.
603
604 Inventory *inv_from = mgr->getInventory(from_inv);
605 Inventory *inv_to = mgr->getInventory(to_inv);
606 if (!inv_from || !inv_to)
607 return;
608
609 InventoryLocation current_player;
610 current_player.setCurrentPlayer();
611 Inventory *inv_player = mgr->getInventory(current_player);
612 if (inv_from != inv_player || inv_to != inv_player)
613 return;
614
615 InventoryList *list_from = inv_from->getList(from_list);
616 InventoryList *list_to = inv_to->getList(to_list);
617 if (!list_from || !list_to)
618 return;
619
620 if (!move_somewhere)
621 list_from->moveItem(from_i, list_to, to_i, count);
622 else
623 list_from->moveItemSomewhere(from_i, list_to, count);
624
625 mgr->setInventoryModified(from_inv);
626 if (inv_from != inv_to)
627 mgr->setInventoryModified(to_inv);
628 }
629
630 /*
631 IDropAction
632 */
633
IDropAction(std::istream & is)634 IDropAction::IDropAction(std::istream &is)
635 {
636 std::string ts;
637
638 std::getline(is, ts, ' ');
639 count = stoi(ts);
640
641 std::getline(is, ts, ' ');
642 from_inv.deSerialize(ts);
643
644 std::getline(is, from_list, ' ');
645
646 std::getline(is, ts, ' ');
647 from_i = stoi(ts);
648 }
649
apply(InventoryManager * mgr,ServerActiveObject * player,IGameDef * gamedef)650 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
651 {
652 Inventory *inv_from = mgr->getInventory(from_inv);
653
654 if (!inv_from) {
655 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
656 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
657 return;
658 }
659
660 InventoryList *list_from = inv_from->getList(from_list);
661
662 /*
663 If a list doesn't exist or the source item doesn't exist
664 */
665 if (!list_from) {
666 infostream<<"IDropAction::apply(): FAIL: source list not found: "
667 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
668 return;
669 }
670 if (list_from->getItem(from_i).empty()) {
671 infostream<<"IDropAction::apply(): FAIL: source item not found: "
672 <<"from_inv=\""<<from_inv.dump()<<"\""
673 <<", from_list=\""<<from_list<<"\""
674 <<" from_i="<<from_i<<std::endl;
675 return;
676 }
677
678 /*
679 Do not handle rollback if inventory is player's
680 */
681 bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
682
683 /*
684 Collect information of endpoints
685 */
686
687 int take_count = list_from->getItem(from_i).count;
688 if (count != 0 && count < take_count)
689 take_count = count;
690 int src_can_take_count = take_count;
691
692 ItemStack src_item = list_from->getItem(from_i);
693 src_item.count = take_count;
694
695 // Run callbacks depending on source inventory
696 switch (from_inv.type) {
697 case InventoryLocation::DETACHED:
698 src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
699 *this, src_item, player);
700 break;
701 case InventoryLocation::NODEMETA:
702 src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
703 *this, src_item, player);
704 break;
705 case InventoryLocation::PLAYER:
706 src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake(
707 *this, src_item, player);
708 break;
709 default:
710 break;
711 }
712
713 if (src_can_take_count != -1 && src_can_take_count < take_count)
714 take_count = src_can_take_count;
715
716 // Update item due executed callbacks
717 src_item = list_from->getItem(from_i);
718
719 // Drop the item
720 ItemStack item1 = list_from->getItem(from_i);
721 item1.count = take_count;
722 if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
723 player->getBasePosition())) {
724 int actually_dropped_count = take_count - item1.count;
725
726 if (actually_dropped_count == 0) {
727 infostream<<"Actually dropped no items"<<std::endl;
728
729 // Revert client prediction. See 'clientApply'
730 if (from_inv.type == InventoryLocation::PLAYER)
731 list_from->setModified();
732 return;
733 }
734
735 // If source isn't infinite
736 if (src_can_take_count != -1) {
737 // Take item from source list
738 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
739
740 if (item2.count != actually_dropped_count)
741 errorstream<<"Could not take dropped count of items"<<std::endl;
742 }
743
744 src_item.count = actually_dropped_count;
745 mgr->setInventoryModified(from_inv);
746 }
747
748 infostream<<"IDropAction::apply(): dropped "
749 <<" from inv=\""<<from_inv.dump()<<"\""
750 <<" list=\""<<from_list<<"\""
751 <<" i="<<from_i
752 <<std::endl;
753
754
755 /*
756 Report drop to endpoints
757 */
758
759 switch (from_inv.type) {
760 case InventoryLocation::DETACHED:
761 PLAYER_TO_SA(player)->detached_inventory_OnTake(
762 *this, src_item, player);
763 break;
764 case InventoryLocation::NODEMETA:
765 PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
766 *this, src_item, player);
767 break;
768 case InventoryLocation::PLAYER:
769 PLAYER_TO_SA(player)->player_inventory_OnTake(
770 *this, src_item, player);
771 break;
772 default:
773 break;
774 }
775
776 /*
777 Record rollback information
778 */
779 if (!ignore_src_rollback && gamedef->rollback()) {
780 IRollbackManager *rollback = gamedef->rollback();
781
782 // If source is not infinite, record item take
783 if (src_can_take_count != -1) {
784 RollbackAction action;
785 std::string loc;
786 {
787 std::ostringstream os(std::ios::binary);
788 from_inv.serialize(os);
789 loc = os.str();
790 }
791 action.setModifyInventoryStack(loc, from_list, from_i,
792 false, src_item);
793 rollback->reportAction(action);
794 }
795 }
796 }
797
clientApply(InventoryManager * mgr,IGameDef * gamedef)798 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
799 {
800 // Optional InventoryAction operation that is run on the client
801 // to make lag less apparent.
802
803 Inventory *inv_from = mgr->getInventory(from_inv);
804 if (!inv_from)
805 return;
806
807 InventoryLocation current_player;
808 current_player.setCurrentPlayer();
809 Inventory *inv_player = mgr->getInventory(current_player);
810 if (inv_from != inv_player)
811 return;
812
813 InventoryList *list_from = inv_from->getList(from_list);
814 if (!list_from)
815 return;
816
817 if (count == 0)
818 list_from->changeItem(from_i, ItemStack());
819 else
820 list_from->takeItem(from_i, count);
821
822 mgr->setInventoryModified(from_inv);
823 }
824
825 /*
826 ICraftAction
827 */
828
ICraftAction(std::istream & is)829 ICraftAction::ICraftAction(std::istream &is)
830 {
831 std::string ts;
832
833 std::getline(is, ts, ' ');
834 count = stoi(ts);
835
836 std::getline(is, ts, ' ');
837 craft_inv.deSerialize(ts);
838 }
839
apply(InventoryManager * mgr,ServerActiveObject * player,IGameDef * gamedef)840 void ICraftAction::apply(InventoryManager *mgr,
841 ServerActiveObject *player, IGameDef *gamedef)
842 {
843 Inventory *inv_craft = mgr->getInventory(craft_inv);
844
845 if (!inv_craft) {
846 infostream << "ICraftAction::apply(): FAIL: inventory not found: "
847 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
848 return;
849 }
850
851 InventoryList *list_craft = inv_craft->getList("craft");
852 InventoryList *list_craftresult = inv_craft->getList("craftresult");
853 InventoryList *list_main = inv_craft->getList("main");
854
855 /*
856 If a list doesn't exist or the source item doesn't exist
857 */
858 if (!list_craft) {
859 infostream << "ICraftAction::apply(): FAIL: craft list not found: "
860 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
861 return;
862 }
863 if (!list_craftresult) {
864 infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
865 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
866 return;
867 }
868 if (list_craftresult->getSize() < 1) {
869 infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
870 << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
871 return;
872 }
873
874 ItemStack crafted;
875 ItemStack craftresultitem;
876 int count_remaining = count;
877 std::vector<ItemStack> output_replacements;
878 getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
879 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
880 bool found = !crafted.empty();
881
882 while (found && list_craftresult->itemFits(0, crafted)) {
883 InventoryList saved_craft_list = *list_craft;
884
885 std::vector<ItemStack> temp;
886 // Decrement input and add crafting output
887 getCraftingResult(inv_craft, crafted, temp, true, gamedef);
888 PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
889 list_craftresult->addItem(0, crafted);
890 mgr->setInventoryModified(craft_inv);
891
892 // Add the new replacements to the list
893 IItemDefManager *itemdef = gamedef->getItemDefManager();
894 for (auto &itemstack : temp) {
895 for (auto &output_replacement : output_replacements) {
896 if (itemstack.name == output_replacement.name) {
897 itemstack = output_replacement.addItem(itemstack, itemdef);
898 if (itemstack.empty())
899 continue;
900 }
901 }
902 output_replacements.push_back(itemstack);
903 }
904
905 actionstream << player->getDescription()
906 << " crafts "
907 << crafted.getItemString()
908 << std::endl;
909
910 // Decrement counter
911 if (count_remaining == 1)
912 break;
913
914 if (count_remaining > 1)
915 count_remaining--;
916
917 // Get next crafting result
918 getCraftingResult(inv_craft, crafted, temp, false, gamedef);
919 PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
920 found = !crafted.empty();
921 }
922
923 // Put the replacements in the inventory or drop them on the floor, if
924 // the inventory is full
925 for (auto &output_replacement : output_replacements) {
926 if (list_main)
927 output_replacement = list_main->addItem(output_replacement);
928 if (output_replacement.empty())
929 continue;
930 u16 count = output_replacement.count;
931 do {
932 PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player,
933 player->getBasePosition());
934 if (count >= output_replacement.count) {
935 errorstream << "Couldn't drop replacement stack " <<
936 output_replacement.getItemString() << " because drop loop didn't "
937 "decrease count." << std::endl;
938
939 break;
940 }
941 } while (!output_replacement.empty());
942 }
943
944 infostream<<"ICraftAction::apply(): crafted "
945 <<" craft_inv=\""<<craft_inv.dump()<<"\""
946 <<std::endl;
947 }
948
clientApply(InventoryManager * mgr,IGameDef * gamedef)949 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
950 {
951 // Optional InventoryAction operation that is run on the client
952 // to make lag less apparent.
953 }
954
955
956 // Crafting helper
getCraftingResult(Inventory * inv,ItemStack & result,std::vector<ItemStack> & output_replacements,bool decrementInput,IGameDef * gamedef)957 bool getCraftingResult(Inventory *inv, ItemStack &result,
958 std::vector<ItemStack> &output_replacements,
959 bool decrementInput, IGameDef *gamedef)
960 {
961 result.clear();
962
963 // Get the InventoryList in which we will operate
964 InventoryList *clist = inv->getList("craft");
965 if (!clist)
966 return false;
967
968 // Mangle crafting grid to an another format
969 CraftInput ci;
970 ci.method = CRAFT_METHOD_NORMAL;
971 ci.width = clist->getWidth() ? clist->getWidth() : 3;
972 for (u16 i=0; i < clist->getSize(); i++)
973 ci.items.push_back(clist->getItem(i));
974
975 // Find out what is crafted and add it to result item slot
976 CraftOutput co;
977 bool found = gamedef->getCraftDefManager()->getCraftResult(
978 ci, co, output_replacements, decrementInput, gamedef);
979 if (found)
980 result.deSerialize(co.item, gamedef->getItemDefManager());
981
982 if (found && decrementInput) {
983 // CraftInput has been changed, apply changes in clist
984 for (u16 i=0; i < clist->getSize(); i++) {
985 clist->changeItem(i, ci.items[i]);
986 }
987 }
988
989 return found;
990 }
991
992