5412a5dca5e1d916858540c3ff68710085cdeff6
[oweals/minetest.git] / src / inventorymanager.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010-2011 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 "log.h"
22 #include "environment.h"
23 #include "scriptapi.h"
24 #include "serverobject.h"
25 #include "main.h"  // for g_settings
26 #include "settings.h"
27 #include "craftdef.h"
28
29 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
30
31 /*
32         InventoryLocation
33 */
34
35 std::string InventoryLocation::dump() const
36 {
37         std::ostringstream os(std::ios::binary);
38         serialize(os);
39         return os.str();
40 }
41
42 void InventoryLocation::serialize(std::ostream &os) const
43 {
44         switch(type){
45         case InventoryLocation::UNDEFINED:
46                 os<<"undefined";
47                 break;
48         case InventoryLocation::CURRENT_PLAYER:
49                 os<<"current_player";
50                 break;
51         case InventoryLocation::PLAYER:
52                 os<<"player:"<<name;
53                 break;
54         case InventoryLocation::NODEMETA:
55                 os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
56                 break;
57         case InventoryLocation::DETACHED:
58                 os<<"detached:"<<name;
59                 break;
60         default:
61                 assert(0);
62         }
63 }
64
65 void InventoryLocation::deSerialize(std::istream &is)
66 {
67         std::string tname;
68         std::getline(is, tname, ':');
69         if(tname == "undefined")
70         {
71                 type = InventoryLocation::UNDEFINED;
72         }
73         else if(tname == "current_player")
74         {
75                 type = InventoryLocation::CURRENT_PLAYER;
76         }
77         else if(tname == "player")
78         {
79                 type = InventoryLocation::PLAYER;
80                 std::getline(is, name, '\n');
81         }
82         else if(tname == "nodemeta")
83         {
84                 type = InventoryLocation::NODEMETA;
85                 std::string pos;
86                 std::getline(is, pos, '\n');
87                 Strfnd fn(pos);
88                 p.X = stoi(fn.next(","));
89                 p.Y = stoi(fn.next(","));
90                 p.Z = stoi(fn.next(","));
91         }
92         else if(tname == "detached")
93         {
94                 type = InventoryLocation::DETACHED;
95                 std::getline(is, name, '\n');
96         }
97         else
98         {
99                 infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
100                 throw SerializationError("Unknown InventoryLocation type");
101         }
102 }
103
104 void InventoryLocation::deSerialize(std::string s)
105 {
106         std::istringstream is(s, std::ios::binary);
107         deSerialize(is);
108 }
109
110 /*
111         InventoryAction
112 */
113
114 InventoryAction * InventoryAction::deSerialize(std::istream &is)
115 {
116         std::string type;
117         std::getline(is, type, ' ');
118
119         InventoryAction *a = NULL;
120
121         if(type == "Move")
122         {
123                 a = new IMoveAction(is);
124         }
125         else if(type == "Drop")
126         {
127                 a = new IDropAction(is);
128         }
129         else if(type == "Craft")
130         {
131                 a = new ICraftAction(is);
132         }
133
134         return a;
135 }
136
137 /*
138         IMoveAction
139 */
140
141 IMoveAction::IMoveAction(std::istream &is)
142 {
143         std::string ts;
144
145         std::getline(is, ts, ' ');
146         count = stoi(ts);
147
148         std::getline(is, ts, ' ');
149         from_inv.deSerialize(ts);
150
151         std::getline(is, from_list, ' ');
152
153         std::getline(is, ts, ' ');
154         from_i = stoi(ts);
155
156         std::getline(is, ts, ' ');
157         to_inv.deSerialize(ts);
158
159         std::getline(is, to_list, ' ');
160
161         std::getline(is, ts, ' ');
162         to_i = stoi(ts);
163 }
164
165 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
166 {
167         Inventory *inv_from = mgr->getInventory(from_inv);
168         Inventory *inv_to = mgr->getInventory(to_inv);
169         
170         if(!inv_from){
171                 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
172                                 <<"from_inv=\""<<from_inv.dump()<<"\""
173                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
174                 return;
175         }
176         if(!inv_to){
177                 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
178                                 <<"from_inv=\""<<from_inv.dump()<<"\""
179                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
180                 return;
181         }
182
183         InventoryList *list_from = inv_from->getList(from_list);
184         InventoryList *list_to = inv_to->getList(to_list);
185
186         /*
187                 If a list doesn't exist or the source item doesn't exist
188         */
189         if(!list_from){
190                 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
191                                 <<"from_inv=\""<<from_inv.dump()<<"\""
192                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
193                 return;
194         }
195         if(!list_to){
196                 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
197                                 <<"to_inv=\""<<to_inv.dump()<<"\""
198                                 <<", to_list=\""<<to_list<<"\""<<std::endl;
199                 return;
200         }
201
202         /*
203                 Collect information of endpoints
204         */
205
206         int try_take_count = count;
207         if(try_take_count == 0)
208                 try_take_count = list_from->getItem(from_i).count;
209
210         int src_can_take_count = 0xffff;
211         int dst_can_put_count = 0xffff;
212         
213         /* Query detached inventories */
214
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)
219         {
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;
225         }
226         else
227         {
228                 // Destination is detached
229                 if(to_inv.type == InventoryLocation::DETACHED)
230                 {
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);
236                 }
237                 // Source is detached
238                 if(from_inv.type == InventoryLocation::DETACHED)
239                 {
240                         lua_State *L = player->getEnv()->getLua();
241                         src_can_take_count = scriptapi_detached_inventory_allow_take(
242                                         L, from_inv.name, from_list, from_i, try_take_count, player);
243                 }
244         }
245
246         /* Query node metadata inventories */
247
248         // Both endpoints are nodemeta
249         // Move occurs in the same nodemeta inventory
250         if(from_inv.type == InventoryLocation::NODEMETA &&
251                         to_inv.type == InventoryLocation::NODEMETA &&
252                         from_inv.p == to_inv.p)
253         {
254                 lua_State *L = player->getEnv()->getLua();
255                 src_can_take_count = scriptapi_nodemeta_inventory_allow_move(
256                                 L, from_inv.p, from_list, from_i,
257                                 to_list, to_i, try_take_count, player);
258                 dst_can_put_count = src_can_take_count;
259         }
260         else
261         {
262                 // Destination is nodemeta
263                 if(to_inv.type == InventoryLocation::NODEMETA)
264                 {
265                         lua_State *L = player->getEnv()->getLua();
266                         ItemStack src_item = list_from->getItem(from_i);
267                         src_item.count = try_take_count;
268                         dst_can_put_count = scriptapi_nodemeta_inventory_allow_put(
269                                         L, to_inv.p, to_list, to_i, src_item, player);
270                 }
271                 // Source is nodemeta
272                 if(from_inv.type == InventoryLocation::NODEMETA)
273                 {
274                         lua_State *L = player->getEnv()->getLua();
275                         src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
276                                         L, from_inv.p, from_list, from_i, try_take_count, player);
277                 }
278         }
279         
280         /* Modify count according to collected data */
281         int new_count = try_take_count;
282         if(new_count > src_can_take_count)
283                 new_count = src_can_take_count;
284         if(new_count > dst_can_put_count)
285                 new_count = dst_can_put_count;
286         
287         /* If no items will be moved, don't go further */
288         if(new_count == 0)
289         {
290                 infostream<<"IMoveAction::apply(): move was completely disallowed: "
291                                 <<" count="<<count
292                                 <<" from inv=\""<<from_inv.dump()<<"\""
293                                 <<" list=\""<<from_list<<"\""
294                                 <<" i="<<from_i
295                                 <<" to inv=\""<<to_inv.dump()<<"\""
296                                 <<" list=\""<<to_list<<"\""
297                                 <<" i="<<to_i
298                                 <<std::endl;
299                 return;
300         }
301
302         count = new_count;
303
304         /*
305                 Perform actual move
306
307                 If something is wrong (source item is empty, destination is the
308                 same as source), nothing happens
309         */
310         list_from->moveItem(from_i, list_to, to_i, count);
311
312         infostream<<"IMoveAction::apply(): moved "
313                         <<" count="<<count
314                         <<" from inv=\""<<from_inv.dump()<<"\""
315                         <<" list=\""<<from_list<<"\""
316                         <<" i="<<from_i
317                         <<" to inv=\""<<to_inv.dump()<<"\""
318                         <<" list=\""<<to_list<<"\""
319                         <<" i="<<to_i
320                         <<std::endl;
321
322         /*
323                 Report move to endpoints
324         */
325         
326         /* Detached inventories */
327
328         // Both endpoints are same detached
329         if(from_inv.type == InventoryLocation::DETACHED &&
330                         to_inv.type == InventoryLocation::DETACHED &&
331                         from_inv.name == to_inv.name)
332         {
333                 lua_State *L = player->getEnv()->getLua();
334                 scriptapi_detached_inventory_on_move(
335                                 L, from_inv.name, from_list, from_i,
336                                 to_list, to_i, count, player);
337         }
338         else
339         {
340                 // Destination is detached
341                 if(to_inv.type == InventoryLocation::DETACHED)
342                 {
343                         lua_State *L = player->getEnv()->getLua();
344                         ItemStack src_item = list_from->getItem(from_i);
345                         src_item.count = count;
346                         scriptapi_detached_inventory_on_put(
347                                         L, to_inv.name, to_list, to_i, src_item, player);
348                 }
349                 // Source is detached
350                 if(from_inv.type == InventoryLocation::DETACHED)
351                 {
352                         lua_State *L = player->getEnv()->getLua();
353                         ItemStack src_item = list_from->getItem(from_i);
354                         src_item.count = count;
355                         scriptapi_detached_inventory_on_take(
356                                         L, from_inv.name, from_list, from_i, src_item.count, player);
357                 }
358         }
359
360         /* Node metadata inventories */
361
362         // Both endpoints are same nodemeta
363         if(from_inv.type == InventoryLocation::NODEMETA &&
364                         to_inv.type == InventoryLocation::NODEMETA &&
365                         from_inv.p == to_inv.p)
366         {
367                 lua_State *L = player->getEnv()->getLua();
368                 scriptapi_nodemeta_inventory_on_move(
369                                 L, from_inv.p, from_list, from_i,
370                                 to_list, to_i, count, player);
371         }
372         else{
373                 // Destination is nodemeta
374                 if(to_inv.type == InventoryLocation::NODEMETA)
375                 {
376                         lua_State *L = player->getEnv()->getLua();
377                         ItemStack src_item = list_from->getItem(from_i);
378                         src_item.count = count;
379                         scriptapi_nodemeta_inventory_on_put(
380                                         L, to_inv.p, to_list, to_i, src_item, player);
381                 }
382                 // Source is nodemeta
383                 else if(from_inv.type == InventoryLocation::NODEMETA)
384                 {
385                         lua_State *L = player->getEnv()->getLua();
386                         ItemStack src_item = list_from->getItem(from_i);
387                         src_item.count = count;
388                         scriptapi_nodemeta_inventory_on_take(
389                                         L, from_inv.p, from_list, from_i, src_item.count, player);
390                 }
391         }
392
393         mgr->setInventoryModified(from_inv);
394         if(inv_from != inv_to)
395                 mgr->setInventoryModified(to_inv);
396 }
397
398 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
399 {
400         // Optional InventoryAction operation that is run on the client
401         // to make lag less apparent.
402
403         Inventory *inv_from = mgr->getInventory(from_inv);
404         Inventory *inv_to = mgr->getInventory(to_inv);
405         if(!inv_from || !inv_to)
406                 return;
407
408         InventoryLocation current_player;
409         current_player.setCurrentPlayer();
410         Inventory *inv_player = mgr->getInventory(current_player);
411         if(inv_from != inv_player || inv_to != inv_player)
412                 return;
413
414         InventoryList *list_from = inv_from->getList(from_list);
415         InventoryList *list_to = inv_to->getList(to_list);
416         if(!list_from || !list_to)
417                 return;
418
419         list_from->moveItem(from_i, list_to, to_i, count);
420
421         mgr->setInventoryModified(from_inv);
422         if(inv_from != inv_to)
423                 mgr->setInventoryModified(to_inv);
424 }
425
426 /*
427         IDropAction
428 */
429
430 IDropAction::IDropAction(std::istream &is)
431 {
432         std::string ts;
433
434         std::getline(is, ts, ' ');
435         count = stoi(ts);
436
437         std::getline(is, ts, ' ');
438         from_inv.deSerialize(ts);
439
440         std::getline(is, from_list, ' ');
441
442         std::getline(is, ts, ' ');
443         from_i = stoi(ts);
444 }
445
446 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
447 {
448         Inventory *inv_from = mgr->getInventory(from_inv);
449         
450         if(!inv_from){
451                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
452                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
453                 return;
454         }
455
456         InventoryList *list_from = inv_from->getList(from_list);
457
458         /*
459                 If a list doesn't exist or the source item doesn't exist
460         */
461         if(!list_from){
462                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
463                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
464                 return;
465         }
466         if(list_from->getItem(from_i).empty())
467         {
468                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
469                                 <<"from_inv=\""<<from_inv.dump()<<"\""
470                                 <<", from_list=\""<<from_list<<"\""
471                                 <<" from_i="<<from_i<<std::endl;
472                 return;
473         }
474
475         /*
476                 Collect information of endpoints
477         */
478
479         int take_count = list_from->getItem(from_i).count;
480         if(count != 0 && count < take_count)
481                 take_count = count;
482         int src_can_take_count = take_count;
483
484         // Source is detached
485         if(from_inv.type == InventoryLocation::DETACHED)
486         {
487                 lua_State *L = player->getEnv()->getLua();
488                 src_can_take_count = scriptapi_detached_inventory_allow_take(
489                                 L, from_inv.name, from_list, from_i, take_count, player);
490         }
491
492         // Source is nodemeta
493         if(from_inv.type == InventoryLocation::NODEMETA)
494         {
495                 lua_State *L = player->getEnv()->getLua();
496                 src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
497                                 L, from_inv.p, from_list, from_i, take_count, player);
498         }
499
500         if(src_can_take_count < take_count)
501                 take_count = src_can_take_count;
502         
503         int actually_dropped_count = 0;
504
505         // Drop the item
506         ItemStack item1 = list_from->getItem(from_i);
507         if(scriptapi_item_on_drop(player->getEnv()->getLua(), item1, player,
508                                 player->getBasePosition() + v3f(0,1,0)))
509         {
510                 actually_dropped_count = take_count - item1.count;
511
512                 if(actually_dropped_count == 0){
513                         infostream<<"Actually dropped no items"<<std::endl;
514                         return;
515                 }
516
517                 // Don't remove from inventory in creative mode
518                 if(g_settings->getBool("creative_mode") == true
519                                 && from_inv.type == InventoryLocation::PLAYER){
520                 }
521                 else{
522                         // Take item from source list
523                         ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
524
525                         if(item2.count != actually_dropped_count)
526                                 errorstream<<"Could not take dropped count of items"<<std::endl;
527                         
528                         mgr->setInventoryModified(from_inv);
529                 }
530         }
531
532         infostream<<"IDropAction::apply(): dropped "
533                         <<" from inv=\""<<from_inv.dump()<<"\""
534                         <<" list=\""<<from_list<<"\""
535                         <<" i="<<from_i
536                         <<std::endl;
537
538         /*
539                 Report drop to endpoints
540         */
541         
542         // Source is detached
543         if(from_inv.type == InventoryLocation::DETACHED)
544         {
545                 lua_State *L = player->getEnv()->getLua();
546                 scriptapi_detached_inventory_on_take(
547                                 L, from_inv.name, from_list, from_i, actually_dropped_count, player);
548         }
549
550         // Source is nodemeta
551         if(from_inv.type == InventoryLocation::NODEMETA)
552         {
553                 lua_State *L = player->getEnv()->getLua();
554                 scriptapi_nodemeta_inventory_on_take(
555                                 L, from_inv.p, from_list, from_i, actually_dropped_count, player);
556         }
557 }
558
559 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
560 {
561         // Optional InventoryAction operation that is run on the client
562         // to make lag less apparent.
563
564         Inventory *inv_from = mgr->getInventory(from_inv);
565         if(!inv_from)
566                 return;
567
568         InventoryLocation current_player;
569         current_player.setCurrentPlayer();
570         Inventory *inv_player = mgr->getInventory(current_player);
571         if(inv_from != inv_player)
572                 return;
573
574         InventoryList *list_from = inv_from->getList(from_list);
575         if(!list_from)
576                 return;
577
578         if(count == 0)
579                 list_from->changeItem(from_i, ItemStack());
580         else
581                 list_from->takeItem(from_i, count);
582
583         mgr->setInventoryModified(from_inv);
584 }
585
586 /*
587         ICraftAction
588 */
589
590 ICraftAction::ICraftAction(std::istream &is)
591 {
592         std::string ts;
593
594         std::getline(is, ts, ' ');
595         count = stoi(ts);
596
597         std::getline(is, ts, ' ');
598         craft_inv.deSerialize(ts);
599 }
600
601 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
602 {
603         Inventory *inv_craft = mgr->getInventory(craft_inv);
604         
605         if(!inv_craft){
606                 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
607                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
608                 return;
609         }
610
611         InventoryList *list_craft = inv_craft->getList("craft");
612         InventoryList *list_craftresult = inv_craft->getList("craftresult");
613
614         /*
615                 If a list doesn't exist or the source item doesn't exist
616         */
617         if(!list_craft){
618                 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
619                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
620                 return;
621         }
622         if(!list_craftresult){
623                 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
624                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
625                 return;
626         }
627         if(list_craftresult->getSize() < 1){
628                 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
629                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
630                 return;
631         }
632
633         ItemStack crafted;
634         int count_remaining = count;
635         bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
636
637         while(found && list_craftresult->itemFits(0, crafted))
638         {
639                 // Decrement input and add crafting output
640                 getCraftingResult(inv_craft, crafted, true, gamedef);
641                 list_craftresult->addItem(0, crafted);
642                 mgr->setInventoryModified(craft_inv);
643
644                 actionstream<<player->getDescription()
645                                 <<" crafts "
646                                 <<crafted.getItemString()
647                                 <<std::endl;
648
649                 // Decrement counter
650                 if(count_remaining == 1)
651                         break;
652                 else if(count_remaining > 1)
653                         count_remaining--;
654
655                 // Get next crafting result
656                 found = getCraftingResult(inv_craft, crafted, false, gamedef);
657         }
658
659         infostream<<"ICraftAction::apply(): crafted "
660                         <<" craft_inv=\""<<craft_inv.dump()<<"\""
661                         <<std::endl;
662 }
663
664 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
665 {
666         // Optional InventoryAction operation that is run on the client
667         // to make lag less apparent.
668 }
669
670
671 // Crafting helper
672 bool getCraftingResult(Inventory *inv, ItemStack& result,
673                 bool decrementInput, IGameDef *gamedef)
674 {
675         DSTACK(__FUNCTION_NAME);
676         
677         result.clear();
678
679         // TODO: Allow different sizes of crafting grids
680
681         // Get the InventoryList in which we will operate
682         InventoryList *clist = inv->getList("craft");
683         if(!clist || clist->getSize() != 9)
684                 return false;
685
686         // Mangle crafting grid to an another format
687         CraftInput ci;
688         ci.method = CRAFT_METHOD_NORMAL;
689         ci.width = 3;
690         for(u16 i=0; i<9; i++)
691                 ci.items.push_back(clist->getItem(i));
692
693         // Find out what is crafted and add it to result item slot
694         CraftOutput co;
695         bool found = gamedef->getCraftDefManager()->getCraftResult(
696                         ci, co, decrementInput, gamedef);
697         if(found)
698                 result.deSerialize(co.item, gamedef->getItemDefManager());
699
700         if(found && decrementInput)
701         {
702                 // CraftInput has been changed, apply changes in clist
703                 for(u16 i=0; i<9; i++)
704                 {
705                         clist->changeItem(i, ci.items[i]);
706                 }
707         }
708
709         return found;
710 }
711