]> git.lizzy.rs Git - minetest.git/blob - src/inventorymanager.cpp
Switch the license to be LGPLv2/later, with small parts still remaining as GPLv2...
[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 "utility.h"
28 #include "craftdef.h"
29
30 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
31
32 /*
33         InventoryLocation
34 */
35
36 std::string InventoryLocation::dump() const
37 {
38         std::ostringstream os(std::ios::binary);
39         serialize(os);
40         return os.str();
41 }
42
43 void InventoryLocation::serialize(std::ostream &os) const
44 {
45         switch(type){
46         case InventoryLocation::UNDEFINED:
47         {
48                 os<<"undefined";
49         }
50         break;
51         case InventoryLocation::CURRENT_PLAYER:
52         {
53                 os<<"current_player";
54         }
55         break;
56         case InventoryLocation::PLAYER:
57         {
58                 os<<"player:"<<name;
59         }
60         break;
61         case InventoryLocation::NODEMETA:
62         {
63                 os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
64         }
65         break;
66         default:
67                 assert(0);
68         }
69 }
70
71 void InventoryLocation::deSerialize(std::istream &is)
72 {
73         std::string tname;
74         std::getline(is, tname, ':');
75         if(tname == "undefined")
76         {
77                 type = InventoryLocation::UNDEFINED;
78         }
79         else if(tname == "current_player")
80         {
81                 type = InventoryLocation::CURRENT_PLAYER;
82         }
83         else if(tname == "player")
84         {
85                 type = InventoryLocation::PLAYER;
86                 std::getline(is, name, '\n');
87         }
88         else if(tname == "nodemeta")
89         {
90                 type = InventoryLocation::NODEMETA;
91                 std::string pos;
92                 std::getline(is, pos, '\n');
93                 Strfnd fn(pos);
94                 p.X = stoi(fn.next(","));
95                 p.Y = stoi(fn.next(","));
96                 p.Z = stoi(fn.next(","));
97         }
98         else
99         {
100                 infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
101                 throw SerializationError("Unknown InventoryLocation type");
102         }
103 }
104
105 void InventoryLocation::deSerialize(std::string s)
106 {
107         std::istringstream is(s, std::ios::binary);
108         deSerialize(is);
109 }
110
111 /*
112         InventoryAction
113 */
114
115 InventoryAction * InventoryAction::deSerialize(std::istream &is)
116 {
117         std::string type;
118         std::getline(is, type, ' ');
119
120         InventoryAction *a = NULL;
121
122         if(type == "Move")
123         {
124                 a = new IMoveAction(is);
125         }
126         else if(type == "Drop")
127         {
128                 a = new IDropAction(is);
129         }
130         else if(type == "Craft")
131         {
132                 a = new ICraftAction(is);
133         }
134
135         return a;
136 }
137
138 /*
139         IMoveAction
140 */
141
142 IMoveAction::IMoveAction(std::istream &is)
143 {
144         std::string ts;
145
146         std::getline(is, ts, ' ');
147         count = stoi(ts);
148
149         std::getline(is, ts, ' ');
150         from_inv.deSerialize(ts);
151
152         std::getline(is, from_list, ' ');
153
154         std::getline(is, ts, ' ');
155         from_i = stoi(ts);
156
157         std::getline(is, ts, ' ');
158         to_inv.deSerialize(ts);
159
160         std::getline(is, to_list, ' ');
161
162         std::getline(is, ts, ' ');
163         to_i = stoi(ts);
164 }
165
166 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
167 {
168         Inventory *inv_from = mgr->getInventory(from_inv);
169         Inventory *inv_to = mgr->getInventory(to_inv);
170         
171         if(!inv_from){
172                 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
173                                 <<"from_inv=\""<<from_inv.dump()<<"\""
174                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
175                 return;
176         }
177         if(!inv_to){
178                 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
179                                 <<"from_inv=\""<<from_inv.dump()<<"\""
180                                 <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl;
181                 return;
182         }
183
184         InventoryList *list_from = inv_from->getList(from_list);
185         InventoryList *list_to = inv_to->getList(to_list);
186
187         /*
188                 If a list doesn't exist or the source item doesn't exist
189         */
190         if(!list_from){
191                 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
192                                 <<"from_inv=\""<<from_inv.dump()<<"\""
193                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
194                 return;
195         }
196         if(!list_to){
197                 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
198                                 <<"to_inv=\""<<to_inv.dump()<<"\""
199                                 <<", to_list=\""<<to_list<<"\""<<std::endl;
200                 return;
201         }
202         
203         // Handle node metadata move
204         if(from_inv.type == InventoryLocation::NODEMETA &&
205                         to_inv.type == InventoryLocation::NODEMETA &&
206                         from_inv.p != to_inv.p)
207         {
208                 errorstream<<"Directly moving items between two nodes is "
209                                 <<"disallowed."<<std::endl;
210                 return;
211         }
212         else if(from_inv.type == InventoryLocation::NODEMETA &&
213                         to_inv.type == InventoryLocation::NODEMETA &&
214                         from_inv.p == to_inv.p)
215         {
216                 lua_State *L = player->getEnv()->getLua();
217                 int count0 = count;
218                 if(count0 == 0)
219                         count0 = list_from->getItem(from_i).count;
220                 infostream<<player->getDescription()<<" moving "<<count0
221                                 <<" items inside node at "<<PP(from_inv.p)<<std::endl;
222                 scriptapi_node_on_metadata_inventory_move(L, from_inv.p,
223                                 from_list, from_i, to_list, to_i, count0, player);
224         }
225         // Handle node metadata take
226         else if(from_inv.type == InventoryLocation::NODEMETA)
227         {
228                 lua_State *L = player->getEnv()->getLua();
229                 int count0 = count;
230                 if(count0 == 0)
231                         count0 = list_from->getItem(from_i).count;
232                 infostream<<player->getDescription()<<" taking "<<count0
233                                 <<" items from node at "<<PP(from_inv.p)<<std::endl;
234                 ItemStack return_stack = scriptapi_node_on_metadata_inventory_take(
235                                 L, from_inv.p, from_list, from_i, count0, player);
236                 if(return_stack.count == 0)
237                         infostream<<"Node metadata gave no items"<<std::endl;
238                 return_stack = list_to->addItem(to_i, return_stack);
239                 list_to->addItem(return_stack); // Force return of everything
240         }
241         // Handle node metadata offer
242         else if(to_inv.type == InventoryLocation::NODEMETA)
243         {
244                 lua_State *L = player->getEnv()->getLua();
245                 int count0 = count;
246                 if(count0 == 0)
247                         count0 = list_from->getItem(from_i).count;
248                 ItemStack offer_stack = list_from->takeItem(from_i, count0);
249                 infostream<<player->getDescription()<<" offering "
250                                 <<offer_stack.count<<" items to node at "
251                                 <<PP(to_inv.p)<<std::endl;
252                 ItemStack reject_stack = scriptapi_node_on_metadata_inventory_offer(
253                                 L, to_inv.p, to_list, to_i, offer_stack, player);
254                 if(reject_stack.count == offer_stack.count)
255                         infostream<<"Node metadata rejected all items"<<std::endl;
256                 else if(reject_stack.count != 0)
257                         infostream<<"Node metadata rejected some items"<<std::endl;
258                 reject_stack = list_from->addItem(from_i, reject_stack);
259                 list_from->addItem(reject_stack); // Force return of everything
260         }
261         // Handle regular move
262         else
263         {
264                 /*
265                         This performs the actual movement
266
267                         If something is wrong (source item is empty, destination is the
268                         same as source), nothing happens
269                 */
270                 list_from->moveItem(from_i, list_to, to_i, count);
271
272                 infostream<<"IMoveAction::apply(): moved "
273                                 <<" count="<<count
274                                 <<" from inv=\""<<from_inv.dump()<<"\""
275                                 <<" list=\""<<from_list<<"\""
276                                 <<" i="<<from_i
277                                 <<" to inv=\""<<to_inv.dump()<<"\""
278                                 <<" list=\""<<to_list<<"\""
279                                 <<" i="<<to_i
280                                 <<std::endl;
281         }
282
283         mgr->setInventoryModified(from_inv);
284         if(inv_from != inv_to)
285                 mgr->setInventoryModified(to_inv);
286 }
287
288 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
289 {
290         // Optional InventoryAction operation that is run on the client
291         // to make lag less apparent.
292
293         Inventory *inv_from = mgr->getInventory(from_inv);
294         Inventory *inv_to = mgr->getInventory(to_inv);
295         if(!inv_from || !inv_to)
296                 return;
297
298         InventoryLocation current_player;
299         current_player.setCurrentPlayer();
300         Inventory *inv_player = mgr->getInventory(current_player);
301         if(inv_from != inv_player || inv_to != inv_player)
302                 return;
303
304         InventoryList *list_from = inv_from->getList(from_list);
305         InventoryList *list_to = inv_to->getList(to_list);
306         if(!list_from || !list_to)
307                 return;
308
309         list_from->moveItem(from_i, list_to, to_i, count);
310
311         mgr->setInventoryModified(from_inv);
312         if(inv_from != inv_to)
313                 mgr->setInventoryModified(to_inv);
314 }
315
316 /*
317         IDropAction
318 */
319
320 IDropAction::IDropAction(std::istream &is)
321 {
322         std::string ts;
323
324         std::getline(is, ts, ' ');
325         count = stoi(ts);
326
327         std::getline(is, ts, ' ');
328         from_inv.deSerialize(ts);
329
330         std::getline(is, from_list, ' ');
331
332         std::getline(is, ts, ' ');
333         from_i = stoi(ts);
334 }
335
336 void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
337 {
338         Inventory *inv_from = mgr->getInventory(from_inv);
339         
340         if(!inv_from){
341                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
342                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
343                 return;
344         }
345
346         InventoryList *list_from = inv_from->getList(from_list);
347
348         /*
349                 If a list doesn't exist or the source item doesn't exist
350         */
351         if(!list_from){
352                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
353                                 <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
354                 return;
355         }
356         if(list_from->getItem(from_i).empty())
357         {
358                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
359                                 <<"from_inv=\""<<from_inv.dump()<<"\""
360                                 <<", from_list=\""<<from_list<<"\""
361                                 <<" from_i="<<from_i<<std::endl;
362                 return;
363         }
364
365         ItemStack item1;
366
367         // Handle node metadata take
368         if(from_inv.type == InventoryLocation::NODEMETA)
369         {
370                 lua_State *L = player->getEnv()->getLua();
371                 int count0 = count;
372                 if(count0 == 0)
373                         count0 = list_from->getItem(from_i).count;
374                 infostream<<player->getDescription()<<" dropping "<<count0
375                                 <<" items from node at "<<PP(from_inv.p)<<std::endl;
376                 ItemStack return_stack = scriptapi_node_on_metadata_inventory_take(
377                                 L, from_inv.p, from_list, from_i, count0, player);
378                 if(return_stack.count == 0)
379                         infostream<<"Node metadata gave no items"<<std::endl;
380                 item1 = return_stack;
381         }
382         else
383         {
384                 // Take item from source list
385                 if(count == 0)
386                         item1 = list_from->changeItem(from_i, ItemStack());
387                 else
388                         item1 = list_from->takeItem(from_i, count);
389         }
390
391         // Drop the item and apply the returned ItemStack
392         ItemStack item2 = item1;
393         if(scriptapi_item_on_drop(player->getEnv()->getLua(), item2, player,
394                                 player->getBasePosition() + v3f(0,1,0)))
395         {
396                 if(g_settings->getBool("creative_mode") == true
397                                 && from_inv.type == InventoryLocation::PLAYER)
398                         item2 = item1;  // creative mode
399
400                 list_from->addItem(from_i, item2);
401
402                 // Unless we have put the same amount back as we took in the first place,
403                 // set inventory modified flag
404                 if(item2.count != item1.count)
405                         mgr->setInventoryModified(from_inv);
406         }
407
408         infostream<<"IDropAction::apply(): dropped "
409                         <<" from inv=\""<<from_inv.dump()<<"\""
410                         <<" list=\""<<from_list<<"\""
411                         <<" i="<<from_i
412                         <<std::endl;
413 }
414
415 void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
416 {
417         // Optional InventoryAction operation that is run on the client
418         // to make lag less apparent.
419
420         Inventory *inv_from = mgr->getInventory(from_inv);
421         if(!inv_from)
422                 return;
423
424         InventoryLocation current_player;
425         current_player.setCurrentPlayer();
426         Inventory *inv_player = mgr->getInventory(current_player);
427         if(inv_from != inv_player)
428                 return;
429
430         InventoryList *list_from = inv_from->getList(from_list);
431         if(!list_from)
432                 return;
433
434         if(count == 0)
435                 list_from->changeItem(from_i, ItemStack());
436         else
437                 list_from->takeItem(from_i, count);
438
439         mgr->setInventoryModified(from_inv);
440 }
441
442 /*
443         ICraftAction
444 */
445
446 ICraftAction::ICraftAction(std::istream &is)
447 {
448         std::string ts;
449
450         std::getline(is, ts, ' ');
451         count = stoi(ts);
452
453         std::getline(is, ts, ' ');
454         craft_inv.deSerialize(ts);
455 }
456
457 void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
458 {
459         Inventory *inv_craft = mgr->getInventory(craft_inv);
460         
461         if(!inv_craft){
462                 infostream<<"ICraftAction::apply(): FAIL: inventory not found: "
463                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
464                 return;
465         }
466
467         InventoryList *list_craft = inv_craft->getList("craft");
468         InventoryList *list_craftresult = inv_craft->getList("craftresult");
469
470         /*
471                 If a list doesn't exist or the source item doesn't exist
472         */
473         if(!list_craft){
474                 infostream<<"ICraftAction::apply(): FAIL: craft list not found: "
475                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
476                 return;
477         }
478         if(!list_craftresult){
479                 infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: "
480                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
481                 return;
482         }
483         if(list_craftresult->getSize() < 1){
484                 infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: "
485                                 <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl;
486                 return;
487         }
488
489         ItemStack crafted;
490         int count_remaining = count;
491         bool found = getCraftingResult(inv_craft, crafted, false, gamedef);
492
493         while(found && list_craftresult->itemFits(0, crafted))
494         {
495                 // Decrement input and add crafting output
496                 getCraftingResult(inv_craft, crafted, true, gamedef);
497                 list_craftresult->addItem(0, crafted);
498                 mgr->setInventoryModified(craft_inv);
499
500                 actionstream<<player->getDescription()
501                                 <<" crafts "
502                                 <<crafted.getItemString()
503                                 <<std::endl;
504
505                 // Decrement counter
506                 if(count_remaining == 1)
507                         break;
508                 else if(count_remaining > 1)
509                         count_remaining--;
510
511                 // Get next crafting result
512                 found = getCraftingResult(inv_craft, crafted, false, gamedef);
513         }
514
515         infostream<<"ICraftAction::apply(): crafted "
516                         <<" craft_inv=\""<<craft_inv.dump()<<"\""
517                         <<std::endl;
518 }
519
520 void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
521 {
522         // Optional InventoryAction operation that is run on the client
523         // to make lag less apparent.
524 }
525
526
527 // Crafting helper
528 bool getCraftingResult(Inventory *inv, ItemStack& result,
529                 bool decrementInput, IGameDef *gamedef)
530 {
531         DSTACK(__FUNCTION_NAME);
532         
533         result.clear();
534
535         // TODO: Allow different sizes of crafting grids
536
537         // Get the InventoryList in which we will operate
538         InventoryList *clist = inv->getList("craft");
539         if(!clist || clist->getSize() != 9)
540                 return false;
541
542         // Mangle crafting grid to an another format
543         CraftInput ci;
544         ci.method = CRAFT_METHOD_NORMAL;
545         ci.width = 3;
546         for(u16 i=0; i<9; i++)
547                 ci.items.push_back(clist->getItem(i));
548
549         // Find out what is crafted and add it to result item slot
550         CraftOutput co;
551         bool found = gamedef->getCraftDefManager()->getCraftResult(
552                         ci, co, decrementInput, gamedef);
553         if(found)
554                 result.deSerialize(co.item, gamedef->getItemDefManager());
555
556         if(found && decrementInput)
557         {
558                 // CraftInput has been changed, apply changes in clist
559                 for(u16 i=0; i<9; i++)
560                 {
561                         clist->changeItem(i, ci.items[i]);
562                 }
563         }
564
565         return found;
566 }
567