]> git.lizzy.rs Git - minetest.git/blob - src/inventorymanager.cpp
Improve formspec positioning
[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                         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);
245                 }
246         }
247
248         /* Query node metadata inventories */
249
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)
255         {
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;
261         }
262         else
263         {
264                 // Destination is nodemeta
265                 if(to_inv.type == InventoryLocation::NODEMETA)
266                 {
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);
272                 }
273                 // Source is nodemeta
274                 if(from_inv.type == InventoryLocation::NODEMETA)
275                 {
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);
281                 }
282         }
283         
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;
290         
291         /* If no items will be moved, don't go further */
292         if(new_count == 0)
293         {
294                 infostream<<"IMoveAction::apply(): move was completely disallowed: "
295                                 <<" count="<<count
296                                 <<" from inv=\""<<from_inv.dump()<<"\""
297                                 <<" list=\""<<from_list<<"\""
298                                 <<" i="<<from_i
299                                 <<" to inv=\""<<to_inv.dump()<<"\""
300                                 <<" list=\""<<to_list<<"\""
301                                 <<" i="<<to_i
302                                 <<std::endl;
303                 return;
304         }
305
306         count = new_count;
307         
308         ItemStack src_item = list_from->getItem(from_i);
309         src_item.count = count;
310
311         /*
312                 Perform actual move
313
314                 If something is wrong (source item is empty, destination is the
315                 same as source), nothing happens
316         */
317         list_from->moveItem(from_i, list_to, to_i, count);
318
319         infostream<<"IMoveAction::apply(): moved "
320                         <<" count="<<count
321                         <<" from inv=\""<<from_inv.dump()<<"\""
322                         <<" list=\""<<from_list<<"\""
323                         <<" i="<<from_i
324                         <<" to inv=\""<<to_inv.dump()<<"\""
325                         <<" list=\""<<to_list<<"\""
326                         <<" i="<<to_i
327                         <<std::endl;
328
329         /*
330                 Report move to endpoints
331         */
332         
333         /* Detached inventories */
334
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)
339         {
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);
344         }
345         else
346         {
347                 // Destination is detached
348                 if(to_inv.type == InventoryLocation::DETACHED)
349                 {
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);
353                 }
354                 // Source is detached
355                 if(from_inv.type == InventoryLocation::DETACHED)
356                 {
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);
360                 }
361         }
362
363         /* Node metadata inventories */
364
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)
369         {
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);
374         }
375         else{
376                 // Destination is nodemeta
377                 if(to_inv.type == InventoryLocation::NODEMETA)
378                 {
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);
382                 }
383                 // Source is nodemeta
384                 else if(from_inv.type == InventoryLocation::NODEMETA)
385                 {
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);
389                 }
390         }
391
392         mgr->setInventoryModified(from_inv);
393         if(inv_from != inv_to)
394                 mgr->setInventoryModified(to_inv);
395 }
396
397 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
398 {
399         // Optional InventoryAction operation that is run on the client
400         // to make lag less apparent.
401
402         Inventory *inv_from = mgr->getInventory(from_inv);
403         Inventory *inv_to = mgr->getInventory(to_inv);
404         if(!inv_from || !inv_to)
405                 return;
406
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)
411                 return;
412
413         InventoryList *list_from = inv_from->getList(from_list);
414         InventoryList *list_to = inv_to->getList(to_list);
415         if(!list_from || !list_to)
416                 return;
417
418         list_from->moveItem(from_i, list_to, to_i, count);
419
420         mgr->setInventoryModified(from_inv);
421         if(inv_from != inv_to)
422                 mgr->setInventoryModified(to_inv);
423 }
424
425 /*
426         IDropAction
427 */
428
429 IDropAction::IDropAction(std::istream &is)
430 {
431         std::string ts;
432
433         std::getline(is, ts, ' ');
434         count = stoi(ts);
435
436         std::getline(is, ts, ' ');
437         from_inv.deSerialize(ts);
438
439         std::getline(is, from_list, ' ');
440
441         std::getline(is, ts, ' ');
442         from_i = stoi(ts);
443 }
444
445 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
446 {
447         Inventory *inv_from = mgr->getInventory(from_inv);
448         
449         if(!inv_from){
450                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
451                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
452                 return;
453         }
454
455         InventoryList *list_from = inv_from->getList(from_list);
456
457         /*
458                 If a list doesn't exist or the source item doesn't exist
459         */
460         if(!list_from){
461                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
462                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
463                 return;
464         }
465         if(list_from->getItem(from_i).empty())
466         {
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;
471                 return;
472         }
473
474         /*
475                 Collect information of endpoints
476         */
477
478         int take_count = list_from->getItem(from_i).count;
479         if(count != 0 && count < take_count)
480                 take_count = count;
481         int src_can_take_count = take_count;
482
483         // Source is detached
484         if(from_inv.type == InventoryLocation::DETACHED)
485         {
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);
491         }
492
493         // Source is nodemeta
494         if(from_inv.type == InventoryLocation::NODEMETA)
495         {
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);
501         }
502
503         if(src_can_take_count < take_count)
504                 take_count = src_can_take_count;
505         
506         int actually_dropped_count = 0;
507
508         ItemStack src_item = list_from->getItem(from_i);
509
510         // Drop the item
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)))
514         {
515                 actually_dropped_count = take_count - item1.count;
516
517                 if(actually_dropped_count == 0){
518                         infostream<<"Actually dropped no items"<<std::endl;
519                         return;
520                 }
521
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         infostream<<"IDropAction::apply(): dropped "
532                         <<" from inv=\""<<from_inv.dump()<<"\""
533                         <<" list=\""<<from_list<<"\""
534                         <<" i="<<from_i
535                         <<std::endl;
536         
537         src_item.count = actually_dropped_count;
538
539         /*
540                 Report drop to endpoints
541         */
542         
543         // Source is detached
544         if(from_inv.type == InventoryLocation::DETACHED)
545         {
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);
549         }
550
551         // Source is nodemeta
552         if(from_inv.type == InventoryLocation::NODEMETA)
553         {
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);
557         }
558 }
559
560 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
561 {
562         // Optional InventoryAction operation that is run on the client
563         // to make lag less apparent.
564
565         Inventory *inv_from = mgr->getInventory(from_inv);
566         if(!inv_from)
567                 return;
568
569         InventoryLocation current_player;
570         current_player.setCurrentPlayer();
571         Inventory *inv_player = mgr->getInventory(current_player);
572         if(inv_from != inv_player)
573                 return;
574
575         InventoryList *list_from = inv_from->getList(from_list);
576         if(!list_from)
577                 return;
578
579         if(count == 0)
580                 list_from->changeItem(from_i, ItemStack());
581         else
582                 list_from->takeItem(from_i, count);
583
584         mgr->setInventoryModified(from_inv);
585 }
586
587 /*
588         ICraftAction
589 */
590
591 ICraftAction::ICraftAction(std::istream &is)
592 {
593         std::string ts;
594
595         std::getline(is, ts, ' ');
596         count = stoi(ts);
597
598         std::getline(is, ts, ' ');
599         craft_inv.deSerialize(ts);
600 }
601
602 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
603 {
604         Inventory *inv_craft = mgr->getInventory(craft_inv);
605         
606         if(!inv_craft){
607                 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
608                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
609                 return;
610         }
611
612         InventoryList *list_craft = inv_craft->getList("craft");
613         InventoryList *list_craftresult = inv_craft->getList("craftresult");
614
615         /*
616                 If a list doesn't exist or the source item doesn't exist
617         */
618         if(!list_craft){
619                 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
620                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
621                 return;
622         }
623         if(!list_craftresult){
624                 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
625                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
626                 return;
627         }
628         if(list_craftresult->getSize() < 1){
629                 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
630                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
631                 return;
632         }
633
634         ItemStack crafted;
635         int count_remaining = count;
636         bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
637
638         while(found && list_craftresult->itemFits(0, crafted))
639         {
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);
644
645                 actionstream<<player->getDescription()
646                                 <<" crafts "
647                                 <<crafted.getItemString()
648                                 <<std::endl;
649
650                 // Decrement counter
651                 if(count_remaining == 1)
652                         break;
653                 else if(count_remaining > 1)
654                         count_remaining--;
655
656                 // Get next crafting result
657                 found = getCraftingResult(inv_craft, crafted, false, gamedef);
658         }
659
660         infostream<<"ICraftAction::apply(): crafted "
661                         <<" craft_inv=\""<<craft_inv.dump()<<"\""
662                         <<std::endl;
663 }
664
665 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
666 {
667         // Optional InventoryAction operation that is run on the client
668         // to make lag less apparent.
669 }
670
671
672 // Crafting helper
673 bool getCraftingResult(Inventory *inv, ItemStack& result,
674                 bool decrementInput, IGameDef *gamedef)
675 {
676         DSTACK(__FUNCTION_NAME);
677         
678         result.clear();
679
680         // TODO: Allow different sizes of crafting grids
681
682         // Get the InventoryList in which we will operate
683         InventoryList *clist = inv->getList("craft");
684         if(!clist || clist->getSize() != 9)
685                 return false;
686
687         // Mangle crafting grid to an another format
688         CraftInput ci;
689         ci.method = CRAFT_METHOD_NORMAL;
690         ci.width = 3;
691         for(u16 i=0; i<9; i++)
692                 ci.items.push_back(clist->getItem(i));
693
694         // Find out what is crafted and add it to result item slot
695         CraftOutput co;
696         bool found = gamedef->getCraftDefManager()->getCraftResult(
697                         ci, co, decrementInput, gamedef);
698         if(found)
699                 result.deSerialize(co.item, gamedef->getItemDefManager());
700
701         if(found && decrementInput)
702         {
703                 // CraftInput has been changed, apply changes in clist
704                 for(u16 i=0; i<9; i++)
705                 {
706                         clist->changeItem(i, ci.items[i]);
707                 }
708         }
709
710         return found;
711 }
712