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 /* Modify count according to collected data */
285 int new_count = try_take_count;
286 if(new_count > src_can_take_count)
287 new_count = src_can_take_count;
288 if(new_count > dst_can_put_count)
289 new_count = dst_can_put_count;
291 /* If no items will be moved, don't go further */
294 infostream<<"IMoveAction::apply(): move was completely disallowed: "
296 <<" from inv=\""<<from_inv.dump()<<"\""
297 <<" list=\""<<from_list<<"\""
299 <<" to inv=\""<<to_inv.dump()<<"\""
300 <<" list=\""<<to_list<<"\""
308 ItemStack src_item = list_from->getItem(from_i);
309 src_item.count = count;
314 If something is wrong (source item is empty, destination is the
315 same as source), nothing happens
317 list_from->moveItem(from_i, list_to, to_i, count);
319 infostream<<"IMoveAction::apply(): moved "
321 <<" from inv=\""<<from_inv.dump()<<"\""
322 <<" list=\""<<from_list<<"\""
324 <<" to inv=\""<<to_inv.dump()<<"\""
325 <<" list=\""<<to_list<<"\""
330 Report move to endpoints
333 /* Detached inventories */
335 // Both endpoints are same detached
336 if(from_inv.type == InventoryLocation::DETACHED &&
337 to_inv.type == InventoryLocation::DETACHED &&
338 from_inv.name == to_inv.name)
340 lua_State *L = player->getEnv()->getLua();
341 scriptapi_detached_inventory_on_move(
342 L, from_inv.name, from_list, from_i,
343 to_list, to_i, count, player);
347 // Destination is detached
348 if(to_inv.type == InventoryLocation::DETACHED)
350 lua_State *L = player->getEnv()->getLua();
351 scriptapi_detached_inventory_on_put(
352 L, to_inv.name, to_list, to_i, src_item, player);
354 // Source is detached
355 if(from_inv.type == InventoryLocation::DETACHED)
357 lua_State *L = player->getEnv()->getLua();
358 scriptapi_detached_inventory_on_take(
359 L, from_inv.name, from_list, from_i, src_item, player);
363 /* Node metadata inventories */
365 // Both endpoints are same nodemeta
366 if(from_inv.type == InventoryLocation::NODEMETA &&
367 to_inv.type == InventoryLocation::NODEMETA &&
368 from_inv.p == to_inv.p)
370 lua_State *L = player->getEnv()->getLua();
371 scriptapi_nodemeta_inventory_on_move(
372 L, from_inv.p, from_list, from_i,
373 to_list, to_i, count, player);
376 // Destination is nodemeta
377 if(to_inv.type == InventoryLocation::NODEMETA)
379 lua_State *L = player->getEnv()->getLua();
380 scriptapi_nodemeta_inventory_on_put(
381 L, to_inv.p, to_list, to_i, src_item, player);
383 // Source is nodemeta
384 else if(from_inv.type == InventoryLocation::NODEMETA)
386 lua_State *L = player->getEnv()->getLua();
387 scriptapi_nodemeta_inventory_on_take(
388 L, from_inv.p, from_list, from_i, src_item, player);
392 mgr->setInventoryModified(from_inv);
393 if(inv_from != inv_to)
394 mgr->setInventoryModified(to_inv);
397 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
399 // Optional InventoryAction operation that is run on the client
400 // to make lag less apparent.
402 Inventory *inv_from = mgr->getInventory(from_inv);
403 Inventory *inv_to = mgr->getInventory(to_inv);
404 if(!inv_from || !inv_to)
407 InventoryLocation current_player;
408 current_player.setCurrentPlayer();
409 Inventory *inv_player = mgr->getInventory(current_player);
410 if(inv_from != inv_player || inv_to != inv_player)
413 InventoryList *list_from = inv_from->getList(from_list);
414 InventoryList *list_to = inv_to->getList(to_list);
415 if(!list_from || !list_to)
418 list_from->moveItem(from_i, list_to, to_i, count);
420 mgr->setInventoryModified(from_inv);
421 if(inv_from != inv_to)
422 mgr->setInventoryModified(to_inv);
429 IDropAction::IDropAction(std::istream &is)
433 std::getline(is, ts, ' ');
436 std::getline(is, ts, ' ');
437 from_inv.deSerialize(ts);
439 std::getline(is, from_list, ' ');
441 std::getline(is, ts, ' ');
445 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
447 Inventory *inv_from = mgr->getInventory(from_inv);
450 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
451 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
455 InventoryList *list_from = inv_from->getList(from_list);
458 If a list doesn't exist or the source item doesn't exist
461 infostream<<"IDropAction::apply(): FAIL: source list not found: "
462 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
465 if(list_from->getItem(from_i).empty())
467 infostream<<"IDropAction::apply(): FAIL: source item not found: "
468 <<"from_inv=\""<<from_inv.dump()<<"\""
469 <<", from_list=\""<<from_list<<"\""
470 <<" from_i="<<from_i<<std::endl;
475 Collect information of endpoints
478 int take_count = list_from->getItem(from_i).count;
479 if(count != 0 && count < take_count)
481 int src_can_take_count = take_count;
483 // Source is detached
484 if(from_inv.type == InventoryLocation::DETACHED)
486 lua_State *L = player->getEnv()->getLua();
487 ItemStack src_item = list_from->getItem(from_i);
488 src_item.count = take_count;
489 src_can_take_count = scriptapi_detached_inventory_allow_take(
490 L, from_inv.name, from_list, from_i, src_item, player);
493 // Source is nodemeta
494 if(from_inv.type == InventoryLocation::NODEMETA)
496 lua_State *L = player->getEnv()->getLua();
497 ItemStack src_item = list_from->getItem(from_i);
498 src_item.count = take_count;
499 src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
500 L, from_inv.p, from_list, from_i, src_item, player);
503 if(src_can_take_count < take_count)
504 take_count = src_can_take_count;
506 int actually_dropped_count = 0;
508 ItemStack src_item = list_from->getItem(from_i);
511 ItemStack item1 = list_from->getItem(from_i);
512 if(scriptapi_item_on_drop(player->getEnv()->getLua(), item1, player,
513 player->getBasePosition() + v3f(0,1,0)))
515 actually_dropped_count = take_count - item1.count;
517 if(actually_dropped_count == 0){
518 infostream<<"Actually dropped no items"<<std::endl;
522 // Take item from source list
523 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
525 if(item2.count != actually_dropped_count)
526 errorstream<<"Could not take dropped count of items"<<std::endl;
528 mgr->setInventoryModified(from_inv);
531 infostream<<"IDropAction::apply(): dropped "
532 <<" from inv=\""<<from_inv.dump()<<"\""
533 <<" list=\""<<from_list<<"\""
537 src_item.count = actually_dropped_count;
540 Report drop to endpoints
543 // Source is detached
544 if(from_inv.type == InventoryLocation::DETACHED)
546 lua_State *L = player->getEnv()->getLua();
547 scriptapi_detached_inventory_on_take(
548 L, from_inv.name, from_list, from_i, src_item, player);
551 // Source is nodemeta
552 if(from_inv.type == InventoryLocation::NODEMETA)
554 lua_State *L = player->getEnv()->getLua();
555 scriptapi_nodemeta_inventory_on_take(
556 L, from_inv.p, from_list, from_i, src_item, player);
560 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
562 // Optional InventoryAction operation that is run on the client
563 // to make lag less apparent.
565 Inventory *inv_from = mgr->getInventory(from_inv);
569 InventoryLocation current_player;
570 current_player.setCurrentPlayer();
571 Inventory *inv_player = mgr->getInventory(current_player);
572 if(inv_from != inv_player)
575 InventoryList *list_from = inv_from->getList(from_list);
580 list_from->changeItem(from_i, ItemStack());
582 list_from->takeItem(from_i, count);
584 mgr->setInventoryModified(from_inv);
591 ICraftAction::ICraftAction(std::istream &is)
595 std::getline(is, ts, ' ');
598 std::getline(is, ts, ' ');
599 craft_inv.deSerialize(ts);
602 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
604 Inventory *inv_craft = mgr->getInventory(craft_inv);
607 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
608 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
612 InventoryList *list_craft = inv_craft->getList("craft");
613 InventoryList *list_craftresult = inv_craft->getList("craftresult");
616 If a list doesn't exist or the source item doesn't exist
619 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
620 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
623 if(!list_craftresult){
624 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
625 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
628 if(list_craftresult->getSize() < 1){
629 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
630 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
635 int count_remaining = count;
636 bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
638 while(found && list_craftresult->itemFits(0, crafted))
640 // Decrement input and add crafting output
641 getCraftingResult(inv_craft, crafted, true, gamedef);
642 list_craftresult->addItem(0, crafted);
643 mgr->setInventoryModified(craft_inv);
645 actionstream<<player->getDescription()
647 <<crafted.getItemString()
651 if(count_remaining == 1)
653 else if(count_remaining > 1)
656 // Get next crafting result
657 found = getCraftingResult(inv_craft, crafted, false, gamedef);
660 infostream<<"ICraftAction::apply(): crafted "
661 <<" craft_inv=\""<<craft_inv.dump()<<"\""
665 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
667 // Optional InventoryAction operation that is run on the client
668 // to make lag less apparent.
673 bool getCraftingResult(Inventory *inv, ItemStack& result,
674 bool decrementInput, IGameDef *gamedef)
676 DSTACK(__FUNCTION_NAME);
680 // TODO: Allow different sizes of crafting grids
682 // Get the InventoryList in which we will operate
683 InventoryList *clist = inv->getList("craft");
684 if(!clist || clist->getSize() != 9)
687 // Mangle crafting grid to an another format
689 ci.method = CRAFT_METHOD_NORMAL;
691 for(u16 i=0; i<9; i++)
692 ci.items.push_back(clist->getItem(i));
694 // Find out what is crafted and add it to result item slot
696 bool found = gamedef->getCraftDefManager()->getCraftResult(
697 ci, co, decrementInput, gamedef);
699 result.deSerialize(co.item, gamedef->getItemDefManager());
701 if(found && decrementInput)
703 // CraftInput has been changed, apply changes in clist
704 for(u16 i=0; i<9; i++)
706 clist->changeItem(i, ci.items[i]);