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