Remove special handling of creative mode
[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                 // Take item from source list
518                 ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
519
520                 if(item2.count != actually_dropped_count)
521                         errorstream<<"Could not take dropped count of items"<<std::endl;
522                 
523                 mgr->setInventoryModified(from_inv);
524         }
525
526         infostream<<"IDropAction::apply(): dropped "
527                         <<" from inv=\""<<from_inv.dump()<<"\""
528                         <<" list=\""<<from_list<<"\""
529                         <<" i="<<from_i
530                         <<std::endl;
531
532         /*
533                 Report drop to endpoints
534         */
535         
536         // Source is detached
537         if(from_inv.type == InventoryLocation::DETACHED)
538         {
539                 lua_State *L = player->getEnv()->getLua();
540                 scriptapi_detached_inventory_on_take(
541                                 L, from_inv.name, from_list, from_i, actually_dropped_count, player);
542         }
543
544         // Source is nodemeta
545         if(from_inv.type == InventoryLocation::NODEMETA)
546         {
547                 lua_State *L = player->getEnv()->getLua();
548                 scriptapi_nodemeta_inventory_on_take(
549                                 L, from_inv.p, from_list, from_i, actually_dropped_count, player);
550         }
551 }
552
553 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
554 {
555         // Optional InventoryAction operation that is run on the client
556         // to make lag less apparent.
557
558         Inventory *inv_from = mgr->getInventory(from_inv);
559         if(!inv_from)
560                 return;
561
562         InventoryLocation current_player;
563         current_player.setCurrentPlayer();
564         Inventory *inv_player = mgr->getInventory(current_player);
565         if(inv_from != inv_player)
566                 return;
567
568         InventoryList *list_from = inv_from->getList(from_list);
569         if(!list_from)
570                 return;
571
572         if(count == 0)
573                 list_from->changeItem(from_i, ItemStack());
574         else
575                 list_from->takeItem(from_i, count);
576
577         mgr->setInventoryModified(from_inv);
578 }
579
580 /*
581         ICraftAction
582 */
583
584 ICraftAction::ICraftAction(std::istream &is)
585 {
586         std::string ts;
587
588         std::getline(is, ts, ' ');
589         count = stoi(ts);
590
591         std::getline(is, ts, ' ');
592         craft_inv.deSerialize(ts);
593 }
594
595 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
596 {
597         Inventory *inv_craft = mgr->getInventory(craft_inv);
598         
599         if(!inv_craft){
600                 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
601                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
602                 return;
603         }
604
605         InventoryList *list_craft = inv_craft->getList("craft");
606         InventoryList *list_craftresult = inv_craft->getList("craftresult");
607
608         /*
609                 If a list doesn't exist or the source item doesn't exist
610         */
611         if(!list_craft){
612                 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
613                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
614                 return;
615         }
616         if(!list_craftresult){
617                 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
618                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
619                 return;
620         }
621         if(list_craftresult->getSize() < 1){
622                 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
623                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
624                 return;
625         }
626
627         ItemStack crafted;
628         int count_remaining = count;
629         bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
630
631         while(found && list_craftresult->itemFits(0, crafted))
632         {
633                 // Decrement input and add crafting output
634                 getCraftingResult(inv_craft, crafted, true, gamedef);
635                 list_craftresult->addItem(0, crafted);
636                 mgr->setInventoryModified(craft_inv);
637
638                 actionstream<<player->getDescription()
639                                 <<" crafts "
640                                 <<crafted.getItemString()
641                                 <<std::endl;
642
643                 // Decrement counter
644                 if(count_remaining == 1)
645                         break;
646                 else if(count_remaining > 1)
647                         count_remaining--;
648
649                 // Get next crafting result
650                 found = getCraftingResult(inv_craft, crafted, false, gamedef);
651         }
652
653         infostream<<"ICraftAction::apply(): crafted "
654                         <<" craft_inv=\""<<craft_inv.dump()<<"\""
655                         <<std::endl;
656 }
657
658 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
659 {
660         // Optional InventoryAction operation that is run on the client
661         // to make lag less apparent.
662 }
663
664
665 // Crafting helper
666 bool getCraftingResult(Inventory *inv, ItemStack& result,
667                 bool decrementInput, IGameDef *gamedef)
668 {
669         DSTACK(__FUNCTION_NAME);
670         
671         result.clear();
672
673         // TODO: Allow different sizes of crafting grids
674
675         // Get the InventoryList in which we will operate
676         InventoryList *clist = inv->getList("craft");
677         if(!clist || clist->getSize() != 9)
678                 return false;
679
680         // Mangle crafting grid to an another format
681         CraftInput ci;
682         ci.method = CRAFT_METHOD_NORMAL;
683         ci.width = 3;
684         for(u16 i=0; i<9; i++)
685                 ci.items.push_back(clist->getItem(i));
686
687         // Find out what is crafted and add it to result item slot
688         CraftOutput co;
689         bool found = gamedef->getCraftDefManager()->getCraftResult(
690                         ci, co, decrementInput, gamedef);
691         if(found)
692                 result.deSerialize(co.item, gamedef->getItemDefManager());
693
694         if(found && decrementInput)
695         {
696                 // CraftInput has been changed, apply changes in clist
697                 for(u16 i=0; i<9; i++)
698                 {
699                         clist->changeItem(i, ci.items[i]);
700                 }
701         }
702
703         return found;
704 }
705