Detached inventories
[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         // Handle node metadata move
203         if(from_inv.type == InventoryLocation::NODEMETA &&
204                         to_inv.type == InventoryLocation::NODEMETA &&
205                         from_inv.p != to_inv.p)
206         {
207                 errorstream<<"Directly moving items between two nodes is "
208                                 <<"disallowed."<<std::endl;
209                 return;
210         }
211         else if(from_inv.type == InventoryLocation::NODEMETA &&
212                         to_inv.type == InventoryLocation::NODEMETA &&
213                         from_inv.p == to_inv.p)
214         {
215                 lua_State *L = player->getEnv()->getLua();
216                 int count0 = count;
217                 if(count0 == 0)
218                         count0 = list_from->getItem(from_i).count;
219                 infostream<<player->getDescription()<<" moving "<<count0
220                                 <<" items inside node at "<<PP(from_inv.p)<<std::endl;
221                 scriptapi_node_on_metadata_inventory_move(L, from_inv.p,
222                                 from_list, from_i, to_list, to_i, count0, player);
223         }
224         // Handle node metadata take
225         else if(from_inv.type == InventoryLocation::NODEMETA)
226         {
227                 lua_State *L = player->getEnv()->getLua();
228                 int count0 = count;
229                 if(count0 == 0)
230                         count0 = list_from->getItem(from_i).count;
231                 infostream<<player->getDescription()<<" taking "<<count0
232                                 <<" items from node at "<<PP(from_inv.p)<<std::endl;
233                 ItemStack return_stack = scriptapi_node_on_metadata_inventory_take(
234                                 L, from_inv.p, from_list, from_i, count0, player);
235                 if(return_stack.count == 0)
236                         infostream<<"Node metadata gave no items"<<std::endl;
237                 return_stack = list_to->addItem(to_i, return_stack);
238                 list_to->addItem(return_stack); // Force return of everything
239         }
240         // Handle node metadata offer
241         else if(to_inv.type == InventoryLocation::NODEMETA)
242         {
243                 lua_State *L = player->getEnv()->getLua();
244                 int count0 = count;
245                 if(count0 == 0)
246                         count0 = list_from->getItem(from_i).count;
247                 ItemStack offer_stack = list_from->takeItem(from_i, count0);
248                 infostream<<player->getDescription()<<" offering "
249                                 <<offer_stack.count<<" items to node at "
250                                 <<PP(to_inv.p)<<std::endl;
251                 ItemStack reject_stack = scriptapi_node_on_metadata_inventory_offer(
252                                 L, to_inv.p, to_list, to_i, offer_stack, player);
253                 if(reject_stack.count == offer_stack.count)
254                         infostream<<"Node metadata rejected all items"<<std::endl;
255                 else if(reject_stack.count != 0)
256                         infostream<<"Node metadata rejected some items"<<std::endl;
257                 reject_stack = list_from->addItem(from_i, reject_stack);
258                 list_from->addItem(reject_stack); // Force return of everything
259         }
260         // Handle regular move
261         else
262         {
263                 /*
264                         This performs the actual movement
265
266                         If something is wrong (source item is empty, destination is the
267                         same as source), nothing happens
268                 */
269                 list_from->moveItem(from_i, list_to, to_i, count);
270
271                 infostream<<"IMoveAction::apply(): moved "
272                                 <<" count="<<count
273                                 <<" from inv=\""<<from_inv.dump()<<"\""
274                                 <<" list=\""<<from_list<<"\""
275                                 <<" i="<<from_i
276                                 <<" to inv=\""<<to_inv.dump()<<"\""
277                                 <<" list=\""<<to_list<<"\""
278                                 <<" i="<<to_i
279                                 <<std::endl;
280         }
281
282         mgr->setInventoryModified(from_inv);
283         if(inv_from != inv_to)
284                 mgr->setInventoryModified(to_inv);
285 }
286
287 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
288 {
289         // Optional InventoryAction operation that is run on the client
290         // to make lag less apparent.
291
292         Inventory *inv_from = mgr->getInventory(from_inv);
293         Inventory *inv_to = mgr->getInventory(to_inv);
294         if(!inv_from || !inv_to)
295                 return;
296
297         InventoryLocation current_player;
298         current_player.setCurrentPlayer();
299         Inventory *inv_player = mgr->getInventory(current_player);
300         if(inv_from != inv_player || inv_to != inv_player)
301                 return;
302
303         InventoryList *list_from = inv_from->getList(from_list);
304         InventoryList *list_to = inv_to->getList(to_list);
305         if(!list_from || !list_to)
306                 return;
307
308         list_from->moveItem(from_i, list_to, to_i, count);
309
310         mgr->setInventoryModified(from_inv);
311         if(inv_from != inv_to)
312                 mgr->setInventoryModified(to_inv);
313 }
314
315 /*
316         IDropAction
317 */
318
319 IDropAction::IDropAction(std::istream &is)
320 {
321         std::string ts;
322
323         std::getline(is, ts, ' ');
324         count = stoi(ts);
325
326         std::getline(is, ts, ' ');
327         from_inv.deSerialize(ts);
328
329         std::getline(is, from_list, ' ');
330
331         std::getline(is, ts, ' ');
332         from_i = stoi(ts);
333 }
334
335 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
336 {
337         Inventory *inv_from = mgr->getInventory(from_inv);
338         
339         if(!inv_from){
340                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
341                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
342                 return;
343         }
344
345         InventoryList *list_from = inv_from->getList(from_list);
346
347         /*
348                 If a list doesn't exist or the source item doesn't exist
349         */
350         if(!list_from){
351                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
352                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
353                 return;
354         }
355         if(list_from->getItem(from_i).empty())
356         {
357                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
358                                 <<"from_inv=\""<<from_inv.dump()<<"\""
359                                 <<", from_list=\""<<from_list<<"\""
360                                 <<" from_i="<<from_i<<std::endl;
361                 return;
362         }
363
364         ItemStack item1;
365
366         // Handle node metadata take
367         if(from_inv.type == InventoryLocation::NODEMETA)
368         {
369                 lua_State *L = player->getEnv()->getLua();
370                 int count0 = count;
371                 if(count0 == 0)
372                         count0 = list_from->getItem(from_i).count;
373                 infostream<<player->getDescription()<<" dropping "<<count0
374                                 <<" items from node at "<<PP(from_inv.p)<<std::endl;
375                 ItemStack return_stack = scriptapi_node_on_metadata_inventory_take(
376                                 L, from_inv.p, from_list, from_i, count0, player);
377                 if(return_stack.count == 0)
378                         infostream<<"Node metadata gave no items"<<std::endl;
379                 item1 = return_stack;
380         }
381         else
382         {
383                 // Take item from source list
384                 if(count == 0)
385                         item1 = list_from->changeItem(from_i, ItemStack());
386                 else
387                         item1 = list_from->takeItem(from_i, count);
388         }
389
390         // Drop the item and apply the returned ItemStack
391         ItemStack item2 = item1;
392         if(scriptapi_item_on_drop(player->getEnv()->getLua(), item2, player,
393                                 player->getBasePosition() + v3f(0,1,0)))
394         {
395                 if(g_settings->getBool("creative_mode") == true
396                                 && from_inv.type == InventoryLocation::PLAYER)
397                         item2 = item1;  // creative mode
398
399                 list_from->addItem(from_i, item2);
400
401                 // Unless we have put the same amount back as we took in the first place,
402                 // set inventory modified flag
403                 if(item2.count != item1.count)
404                         mgr->setInventoryModified(from_inv);
405         }
406
407         infostream<<"IDropAction::apply(): dropped "
408                         <<" from inv=\""<<from_inv.dump()<<"\""
409                         <<" list=\""<<from_list<<"\""
410                         <<" i="<<from_i
411                         <<std::endl;
412 }
413
414 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
415 {
416         // Optional InventoryAction operation that is run on the client
417         // to make lag less apparent.
418
419         Inventory *inv_from = mgr->getInventory(from_inv);
420         if(!inv_from)
421                 return;
422
423         InventoryLocation current_player;
424         current_player.setCurrentPlayer();
425         Inventory *inv_player = mgr->getInventory(current_player);
426         if(inv_from != inv_player)
427                 return;
428
429         InventoryList *list_from = inv_from->getList(from_list);
430         if(!list_from)
431                 return;
432
433         if(count == 0)
434                 list_from->changeItem(from_i, ItemStack());
435         else
436                 list_from->takeItem(from_i, count);
437
438         mgr->setInventoryModified(from_inv);
439 }
440
441 /*
442         ICraftAction
443 */
444
445 ICraftAction::ICraftAction(std::istream &is)
446 {
447         std::string ts;
448
449         std::getline(is, ts, ' ');
450         count = stoi(ts);
451
452         std::getline(is, ts, ' ');
453         craft_inv.deSerialize(ts);
454 }
455
456 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
457 {
458         Inventory *inv_craft = mgr->getInventory(craft_inv);
459         
460         if(!inv_craft){
461                 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
462                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
463                 return;
464         }
465
466         InventoryList *list_craft = inv_craft->getList("craft");
467         InventoryList *list_craftresult = inv_craft->getList("craftresult");
468
469         /*
470                 If a list doesn't exist or the source item doesn't exist
471         */
472         if(!list_craft){
473                 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
474                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
475                 return;
476         }
477         if(!list_craftresult){
478                 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
479                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
480                 return;
481         }
482         if(list_craftresult->getSize() < 1){
483                 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
484                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
485                 return;
486         }
487
488         ItemStack crafted;
489         int count_remaining = count;
490         bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
491
492         while(found && list_craftresult->itemFits(0, crafted))
493         {
494                 // Decrement input and add crafting output
495                 getCraftingResult(inv_craft, crafted, true, gamedef);
496                 list_craftresult->addItem(0, crafted);
497                 mgr->setInventoryModified(craft_inv);
498
499                 actionstream<<player->getDescription()
500                                 <<" crafts "
501                                 <<crafted.getItemString()
502                                 <<std::endl;
503
504                 // Decrement counter
505                 if(count_remaining == 1)
506                         break;
507                 else if(count_remaining > 1)
508                         count_remaining--;
509
510                 // Get next crafting result
511                 found = getCraftingResult(inv_craft, crafted, false, gamedef);
512         }
513
514         infostream<<"ICraftAction::apply(): crafted "
515                         <<" craft_inv=\""<<craft_inv.dump()<<"\""
516                         <<std::endl;
517 }
518
519 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
520 {
521         // Optional InventoryAction operation that is run on the client
522         // to make lag less apparent.
523 }
524
525
526 // Crafting helper
527 bool getCraftingResult(Inventory *inv, ItemStack& result,
528                 bool decrementInput, IGameDef *gamedef)
529 {
530         DSTACK(__FUNCTION_NAME);
531         
532         result.clear();
533
534         // TODO: Allow different sizes of crafting grids
535
536         // Get the InventoryList in which we will operate
537         InventoryList *clist = inv->getList("craft");
538         if(!clist || clist->getSize() != 9)
539                 return false;
540
541         // Mangle crafting grid to an another format
542         CraftInput ci;
543         ci.method = CRAFT_METHOD_NORMAL;
544         ci.width = 3;
545         for(u16 i=0; i<9; i++)
546                 ci.items.push_back(clist->getItem(i));
547
548         // Find out what is crafted and add it to result item slot
549         CraftOutput co;
550         bool found = gamedef->getCraftDefManager()->getCraftResult(
551                         ci, co, decrementInput, gamedef);
552         if(found)
553                 result.deSerialize(co.item, gamedef->getItemDefManager());
554
555         if(found && decrementInput)
556         {
557                 // CraftInput has been changed, apply changes in clist
558                 for(u16 i=0; i<9; i++)
559                 {
560                         clist->changeItem(i, ci.items[i]);
561                 }
562         }
563
564         return found;
565 }
566