3 Copyright (C) 2010-2011 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 "environment.h"
23 #include "scriptapi.h"
24 #include "serverobject.h"
25 #include "main.h" // for g_settings
29 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
35 std::string InventoryLocation::dump() const
37 std::ostringstream os(std::ios::binary);
42 void InventoryLocation::serialize(std::ostream &os) const
45 case InventoryLocation::UNDEFINED:
48 case InventoryLocation::CURRENT_PLAYER:
51 case InventoryLocation::PLAYER:
54 case InventoryLocation::NODEMETA:
55 os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
57 case InventoryLocation::DETACHED:
58 os<<"detached:"<<name;
65 void InventoryLocation::deSerialize(std::istream &is)
68 std::getline(is, tname, ':');
69 if(tname == "undefined")
71 type = InventoryLocation::UNDEFINED;
73 else if(tname == "current_player")
75 type = InventoryLocation::CURRENT_PLAYER;
77 else if(tname == "player")
79 type = InventoryLocation::PLAYER;
80 std::getline(is, name, '\n');
82 else if(tname == "nodemeta")
84 type = InventoryLocation::NODEMETA;
86 std::getline(is, pos, '\n');
88 p.X = stoi(fn.next(","));
89 p.Y = stoi(fn.next(","));
90 p.Z = stoi(fn.next(","));
92 else if(tname == "detached")
94 type = InventoryLocation::DETACHED;
95 std::getline(is, name, '\n');
99 infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
100 throw SerializationError("Unknown InventoryLocation type");
104 void InventoryLocation::deSerialize(std::string s)
106 std::istringstream is(s, std::ios::binary);
114 InventoryAction * InventoryAction::deSerialize(std::istream &is)
117 std::getline(is, type, ' ');
119 InventoryAction *a = NULL;
123 a = new IMoveAction(is);
125 else if(type == "Drop")
127 a = new IDropAction(is);
129 else if(type == "Craft")
131 a = new ICraftAction(is);
141 IMoveAction::IMoveAction(std::istream &is)
145 std::getline(is, ts, ' ');
148 std::getline(is, ts, ' ');
149 from_inv.deSerialize(ts);
151 std::getline(is, from_list, ' ');
153 std::getline(is, ts, ' ');
156 std::getline(is, ts, ' ');
157 to_inv.deSerialize(ts);
159 std::getline(is, to_list, ' ');
161 std::getline(is, ts, ' ');
165 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
167 Inventory *inv_from = mgr->getInventory(from_inv);
168 Inventory *inv_to = mgr->getInventory(to_inv);
171 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
172 <<"from_inv=\""<<from_inv.dump()<<"\""
173 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
177 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
178 <<"from_inv=\""<<from_inv.dump()<<"\""
179 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
183 InventoryList *list_from = inv_from->getList(from_list);
184 InventoryList *list_to = inv_to->getList(to_list);
187 If a list doesn't exist or the source item doesn't exist
190 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
191 <<"from_inv=\""<<from_inv.dump()<<"\""
192 <<", from_list=\""<<from_list<<"\""<<std::endl;
196 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
197 <<"to_inv=\""<<to_inv.dump()<<"\""
198 <<", to_list=\""<<to_list<<"\""<<std::endl;
203 Collect information of endpoints
206 int try_take_count = count;
207 if(try_take_count == 0)
208 try_take_count = list_from->getItem(from_i).count;
210 int src_can_take_count = 0xffff;
211 int dst_can_put_count = 0xffff;
213 /* Query detached inventories */
215 // Move occurs in the same detached inventory
216 if(from_inv.type == InventoryLocation::DETACHED &&
217 to_inv.type == InventoryLocation::DETACHED &&
218 from_inv.name == to_inv.name)
220 lua_State *L = player->getEnv()->getLua();
221 src_can_take_count = scriptapi_detached_inventory_allow_move(
222 L, from_inv.name, from_list, from_i,
223 to_list, to_i, try_take_count, player);
224 dst_can_put_count = src_can_take_count;
228 // Destination is detached
229 if(to_inv.type == InventoryLocation::DETACHED)
231 lua_State *L = player->getEnv()->getLua();
232 ItemStack src_item = list_from->getItem(from_i);
233 src_item.count = try_take_count;
234 dst_can_put_count = scriptapi_detached_inventory_allow_put(
235 L, to_inv.name, to_list, to_i, src_item, player);
237 // Source is detached
238 if(from_inv.type == InventoryLocation::DETACHED)
240 lua_State *L = player->getEnv()->getLua();
241 ItemStack src_item = list_from->getItem(from_i);
242 src_item.count = try_take_count;
243 src_can_take_count = scriptapi_detached_inventory_allow_take(
244 L, from_inv.name, from_list, from_i, src_item, player);
248 /* Query node metadata inventories */
250 // Both endpoints are nodemeta
251 // Move occurs in the same nodemeta inventory
252 if(from_inv.type == InventoryLocation::NODEMETA &&
253 to_inv.type == InventoryLocation::NODEMETA &&
254 from_inv.p == to_inv.p)
256 lua_State *L = player->getEnv()->getLua();
257 src_can_take_count = scriptapi_nodemeta_inventory_allow_move(
258 L, from_inv.p, from_list, from_i,
259 to_list, to_i, try_take_count, player);
260 dst_can_put_count = src_can_take_count;
264 // Destination is nodemeta
265 if(to_inv.type == InventoryLocation::NODEMETA)
267 lua_State *L = player->getEnv()->getLua();
268 ItemStack src_item = list_from->getItem(from_i);
269 src_item.count = try_take_count;
270 dst_can_put_count = scriptapi_nodemeta_inventory_allow_put(
271 L, to_inv.p, to_list, to_i, src_item, player);
273 // Source is nodemeta
274 if(from_inv.type == InventoryLocation::NODEMETA)
276 lua_State *L = player->getEnv()->getLua();
277 ItemStack src_item = list_from->getItem(from_i);
278 src_item.count = try_take_count;
279 src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
280 L, from_inv.p, from_list, from_i, src_item, player);
284 int old_count = count;
286 /* Modify count according to collected data */
287 count = try_take_count;
288 if(src_can_take_count != -1 && count > src_can_take_count)
289 count = src_can_take_count;
290 if(dst_can_put_count != -1 && count > dst_can_put_count)
291 count = dst_can_put_count;
292 /* Limit according to source item count */
293 if(count > list_from->getItem(from_i).count)
294 count = list_from->getItem(from_i).count;
296 /* If no items will be moved, don't go further */
299 infostream<<"IMoveAction::apply(): move was completely disallowed:"
300 <<" count="<<old_count
301 <<" from inv=\""<<from_inv.dump()<<"\""
302 <<" list=\""<<from_list<<"\""
304 <<" to inv=\""<<to_inv.dump()<<"\""
305 <<" list=\""<<to_list<<"\""
311 ItemStack src_item = list_from->getItem(from_i);
312 src_item.count = count;
313 ItemStack from_stack_was = list_from->getItem(from_i);
314 ItemStack to_stack_was = list_to->getItem(to_i);
319 If something is wrong (source item is empty, destination is the
320 same as source), nothing happens
322 list_from->moveItem(from_i, list_to, to_i, count);
324 // If source is infinite, reset it's stack
325 if(src_can_take_count == -1){
326 list_from->deleteItem(from_i);
327 list_from->addItem(from_i, from_stack_was);
329 // If destination is infinite, reset it's stack and take count from source
330 if(dst_can_put_count == -1){
331 list_to->deleteItem(to_i);
332 list_to->addItem(to_i, to_stack_was);
333 list_from->takeItem(from_i, count);
336 infostream<<"IMoveAction::apply(): moved"
338 <<" from inv=\""<<from_inv.dump()<<"\""
339 <<" list=\""<<from_list<<"\""
341 <<" to inv=\""<<to_inv.dump()<<"\""
342 <<" list=\""<<to_list<<"\""
347 Report move to endpoints
350 /* Detached inventories */
352 // Both endpoints are same detached
353 if(from_inv.type == InventoryLocation::DETACHED &&
354 to_inv.type == InventoryLocation::DETACHED &&
355 from_inv.name == to_inv.name)
357 lua_State *L = player->getEnv()->getLua();
358 scriptapi_detached_inventory_on_move(
359 L, from_inv.name, from_list, from_i,
360 to_list, to_i, count, player);
364 // Destination is detached
365 if(to_inv.type == InventoryLocation::DETACHED)
367 lua_State *L = player->getEnv()->getLua();
368 scriptapi_detached_inventory_on_put(
369 L, to_inv.name, to_list, to_i, src_item, player);
371 // Source is detached
372 if(from_inv.type == InventoryLocation::DETACHED)
374 lua_State *L = player->getEnv()->getLua();
375 scriptapi_detached_inventory_on_take(
376 L, from_inv.name, from_list, from_i, src_item, player);
380 /* Node metadata inventories */
382 // Both endpoints are same nodemeta
383 if(from_inv.type == InventoryLocation::NODEMETA &&
384 to_inv.type == InventoryLocation::NODEMETA &&
385 from_inv.p == to_inv.p)
387 lua_State *L = player->getEnv()->getLua();
388 scriptapi_nodemeta_inventory_on_move(
389 L, from_inv.p, from_list, from_i,
390 to_list, to_i, count, player);
393 // Destination is nodemeta
394 if(to_inv.type == InventoryLocation::NODEMETA)
396 lua_State *L = player->getEnv()->getLua();
397 scriptapi_nodemeta_inventory_on_put(
398 L, to_inv.p, to_list, to_i, src_item, player);
400 // Source is nodemeta
401 else if(from_inv.type == InventoryLocation::NODEMETA)
403 lua_State *L = player->getEnv()->getLua();
404 scriptapi_nodemeta_inventory_on_take(
405 L, from_inv.p, from_list, from_i, src_item, player);
409 mgr->setInventoryModified(from_inv);
410 if(inv_from != inv_to)
411 mgr->setInventoryModified(to_inv);
414 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
416 // Optional InventoryAction operation that is run on the client
417 // to make lag less apparent.
419 Inventory *inv_from = mgr->getInventory(from_inv);
420 Inventory *inv_to = mgr->getInventory(to_inv);
421 if(!inv_from || !inv_to)
424 InventoryLocation current_player;
425 current_player.setCurrentPlayer();
426 Inventory *inv_player = mgr->getInventory(current_player);
427 if(inv_from != inv_player || inv_to != inv_player)
430 InventoryList *list_from = inv_from->getList(from_list);
431 InventoryList *list_to = inv_to->getList(to_list);
432 if(!list_from || !list_to)
435 list_from->moveItem(from_i, list_to, to_i, count);
437 mgr->setInventoryModified(from_inv);
438 if(inv_from != inv_to)
439 mgr->setInventoryModified(to_inv);
446 IDropAction::IDropAction(std::istream &is)
450 std::getline(is, ts, ' ');
453 std::getline(is, ts, ' ');
454 from_inv.deSerialize(ts);
456 std::getline(is, from_list, ' ');
458 std::getline(is, ts, ' ');
462 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
464 Inventory *inv_from = mgr->getInventory(from_inv);
467 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
468 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
472 InventoryList *list_from = inv_from->getList(from_list);
475 If a list doesn't exist or the source item doesn't exist
478 infostream<<"IDropAction::apply(): FAIL: source list not found: "
479 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
482 if(list_from->getItem(from_i).empty())
484 infostream<<"IDropAction::apply(): FAIL: source item not found: "
485 <<"from_inv=\""<<from_inv.dump()<<"\""
486 <<", from_list=\""<<from_list<<"\""
487 <<" from_i="<<from_i<<std::endl;
492 Collect information of endpoints
495 int take_count = list_from->getItem(from_i).count;
496 if(count != 0 && count < take_count)
498 int src_can_take_count = take_count;
500 // Source is detached
501 if(from_inv.type == InventoryLocation::DETACHED)
503 lua_State *L = player->getEnv()->getLua();
504 ItemStack src_item = list_from->getItem(from_i);
505 src_item.count = take_count;
506 src_can_take_count = scriptapi_detached_inventory_allow_take(
507 L, from_inv.name, from_list, from_i, src_item, player);
510 // Source is nodemeta
511 if(from_inv.type == InventoryLocation::NODEMETA)
513 lua_State *L = player->getEnv()->getLua();
514 ItemStack src_item = list_from->getItem(from_i);
515 src_item.count = take_count;
516 src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
517 L, from_inv.p, from_list, from_i, src_item, player);
520 if(src_can_take_count != -1 && src_can_take_count < take_count)
521 take_count = src_can_take_count;
523 int actually_dropped_count = 0;
525 ItemStack src_item = list_from->getItem(from_i);
528 ItemStack item1 = list_from->getItem(from_i);
529 if(scriptapi_item_on_drop(player->getEnv()->getLua(), item1, player,
530 player->getBasePosition() + v3f(0,1,0)))
532 actually_dropped_count = take_count - item1.count;
534 if(actually_dropped_count == 0){
535 infostream<<"Actually dropped no items"<<std::endl;
539 // If source isn't infinite
540 if(src_can_take_count != -1){
541 // Take item from source list
542 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
544 if(item2.count != actually_dropped_count)
545 errorstream<<"Could not take dropped count of items"<<std::endl;
547 mgr->setInventoryModified(from_inv);
551 infostream<<"IDropAction::apply(): dropped "
552 <<" from inv=\""<<from_inv.dump()<<"\""
553 <<" list=\""<<from_list<<"\""
557 src_item.count = actually_dropped_count;
560 Report drop to endpoints
563 // Source is detached
564 if(from_inv.type == InventoryLocation::DETACHED)
566 lua_State *L = player->getEnv()->getLua();
567 scriptapi_detached_inventory_on_take(
568 L, from_inv.name, from_list, from_i, src_item, player);
571 // Source is nodemeta
572 if(from_inv.type == InventoryLocation::NODEMETA)
574 lua_State *L = player->getEnv()->getLua();
575 scriptapi_nodemeta_inventory_on_take(
576 L, from_inv.p, from_list, from_i, src_item, player);
580 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
582 // Optional InventoryAction operation that is run on the client
583 // to make lag less apparent.
585 Inventory *inv_from = mgr->getInventory(from_inv);
589 InventoryLocation current_player;
590 current_player.setCurrentPlayer();
591 Inventory *inv_player = mgr->getInventory(current_player);
592 if(inv_from != inv_player)
595 InventoryList *list_from = inv_from->getList(from_list);
600 list_from->changeItem(from_i, ItemStack());
602 list_from->takeItem(from_i, count);
604 mgr->setInventoryModified(from_inv);
611 ICraftAction::ICraftAction(std::istream &is)
615 std::getline(is, ts, ' ');
618 std::getline(is, ts, ' ');
619 craft_inv.deSerialize(ts);
622 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
624 Inventory *inv_craft = mgr->getInventory(craft_inv);
627 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
628 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
632 InventoryList *list_craft = inv_craft->getList("craft");
633 InventoryList *list_craftresult = inv_craft->getList("craftresult");
636 If a list doesn't exist or the source item doesn't exist
639 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
640 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
643 if(!list_craftresult){
644 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
645 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
648 if(list_craftresult->getSize() < 1){
649 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
650 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
655 int count_remaining = count;
656 bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
658 while(found && list_craftresult->itemFits(0, crafted))
660 // Decrement input and add crafting output
661 getCraftingResult(inv_craft, crafted, true, gamedef);
662 list_craftresult->addItem(0, crafted);
663 mgr->setInventoryModified(craft_inv);
665 actionstream<<player->getDescription()
667 <<crafted.getItemString()
671 if(count_remaining == 1)
673 else if(count_remaining > 1)
676 // Get next crafting result
677 found = getCraftingResult(inv_craft, crafted, false, gamedef);
680 infostream<<"ICraftAction::apply(): crafted "
681 <<" craft_inv=\""<<craft_inv.dump()<<"\""
685 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
687 // Optional InventoryAction operation that is run on the client
688 // to make lag less apparent.
693 bool getCraftingResult(Inventory *inv, ItemStack& result,
694 bool decrementInput, IGameDef *gamedef)
696 DSTACK(__FUNCTION_NAME);
700 // TODO: Allow different sizes of crafting grids
702 // Get the InventoryList in which we will operate
703 InventoryList *clist = inv->getList("craft");
704 if(!clist || clist->getSize() != 9)
707 // Mangle crafting grid to an another format
709 ci.method = CRAFT_METHOD_NORMAL;
711 for(u16 i=0; i<9; i++)
712 ci.items.push_back(clist->getItem(i));
714 // Find out what is crafted and add it to result item slot
716 bool found = gamedef->getCraftDefManager()->getCraftResult(
717 ci, co, decrementInput, gamedef);
719 result.deSerialize(co.item, gamedef->getItemDefManager());
721 if(found && decrementInput)
723 // CraftInput has been changed, apply changes in clist
724 for(u16 i=0; i<9; i++)
726 clist->changeItem(i, ci.items[i]);