195438d266013415799ac99205b296b9f252f21e
[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 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 "serverremoteplayer.h"
22 #include "log.h"
23 #include "mapblock.h" // getNodeBlockPos
24
25 /*
26         InventoryManager
27 */
28
29 // Wrapper for old code
30 Inventory* InventoryManager::getInventory(InventoryContext *c, std::string id)
31 {
32         if(id == "current_player")
33         {
34                 assert(c->current_player);
35                 InventoryLocation loc;
36                 loc.setPlayer(c->current_player->getName());
37                 return getInventory(loc);
38         }
39         
40         Strfnd fn(id);
41         std::string id0 = fn.next(":");
42
43         if(id0 == "nodemeta")
44         {
45                 v3s16 p;
46                 p.X = stoi(fn.next(","));
47                 p.Y = stoi(fn.next(","));
48                 p.Z = stoi(fn.next(","));
49
50                 InventoryLocation loc;
51                 loc.setNodeMeta(p);
52                 return getInventory(loc);
53         }
54
55         errorstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl;
56         return NULL;
57 }
58 // Wrapper for old code
59 void InventoryManager::inventoryModified(InventoryContext *c, std::string id)
60 {
61         if(id == "current_player")
62         {
63                 assert(c->current_player);
64                 InventoryLocation loc;
65                 loc.setPlayer(c->current_player->getName());
66                 setInventoryModified(loc);
67                 return;
68         }
69         
70         Strfnd fn(id);
71         std::string id0 = fn.next(":");
72
73         if(id0 == "nodemeta")
74         {
75                 v3s16 p;
76                 p.X = stoi(fn.next(","));
77                 p.Y = stoi(fn.next(","));
78                 p.Z = stoi(fn.next(","));
79                 v3s16 blockpos = getNodeBlockPos(p);
80
81                 InventoryLocation loc;
82                 loc.setNodeMeta(p);
83                 setInventoryModified(loc);
84                 return;
85         }
86
87         errorstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl;
88 }
89
90 /*
91         InventoryAction
92 */
93
94 InventoryAction * InventoryAction::deSerialize(std::istream &is)
95 {
96         std::string type;
97         std::getline(is, type, ' ');
98
99         InventoryAction *a = NULL;
100
101         if(type == "Move")
102         {
103                 a = new IMoveAction(is);
104         }
105         else if(type == "Drop")
106         {
107                 a = new IDropAction(is);
108         }
109
110         return a;
111 }
112
113 static std::string describeC(const struct InventoryContext *c)
114 {
115         if(c->current_player == NULL)
116                 return "current_player=NULL";
117         else
118                 return std::string("current_player=") + c->current_player->getName();
119 }
120
121 IMoveAction::IMoveAction(std::istream &is)
122 {
123         std::string ts;
124
125         std::getline(is, ts, ' ');
126         count = stoi(ts);
127
128         std::getline(is, from_inv, ' ');
129
130         std::getline(is, from_list, ' ');
131
132         std::getline(is, ts, ' ');
133         from_i = stoi(ts);
134
135         std::getline(is, to_inv, ' ');
136
137         std::getline(is, to_list, ' ');
138
139         std::getline(is, ts, ' ');
140         to_i = stoi(ts);
141 }
142
143 void IMoveAction::apply(InventoryContext *c, InventoryManager *mgr,
144                 ServerEnvironment *env)
145 {
146         Inventory *inv_from = mgr->getInventory(c, from_inv);
147         Inventory *inv_to = mgr->getInventory(c, to_inv);
148         
149         if(!inv_from){
150                 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
151                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
152                                 <<", to_inv=\""<<to_inv<<"\""<<std::endl;
153                 return;
154         }
155         if(!inv_to){
156                 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
157                                 "context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
158                                 <<", to_inv=\""<<to_inv<<"\""<<std::endl;
159                 return;
160         }
161
162         InventoryList *list_from = inv_from->getList(from_list);
163         InventoryList *list_to = inv_to->getList(to_list);
164
165         /*
166                 If a list doesn't exist or the source item doesn't exist
167         */
168         if(!list_from){
169                 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
170                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
171                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
172                 return;
173         }
174         if(!list_to){
175                 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
176                                 <<"context=["<<describeC(c)<<"], to_inv=\""<<to_inv<<"\""
177                                 <<", to_list=\""<<to_list<<"\""<<std::endl;
178                 return;
179         }
180         if(list_from->getItem(from_i) == NULL)
181         {
182                 infostream<<"IMoveAction::apply(): FAIL: source item not found: "
183                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
184                                 <<", from_list=\""<<from_list<<"\""
185                                 <<" from_i="<<from_i<<std::endl;
186                 return;
187         }
188         /*
189                 If the source and the destination slots are the same
190         */
191         if(inv_from == inv_to && list_from == list_to && from_i == to_i)
192         {
193                 infostream<<"IMoveAction::apply(): FAIL: source and destination slots "
194                                 <<"are the same: inv=\""<<from_inv<<"\" list=\""<<from_list
195                                 <<"\" i="<<from_i<<std::endl;
196                 return;
197         }
198         
199         // Take item from source list
200         InventoryItem *item1 = NULL;
201         if(count == 0)
202                 item1 = list_from->changeItem(from_i, NULL);
203         else
204                 item1 = list_from->takeItem(from_i, count);
205
206         // Try to add the item to destination list
207         InventoryItem *olditem = item1;
208         item1 = list_to->addItem(to_i, item1);
209
210         // If something is returned, the item was not fully added
211         if(item1 != NULL)
212         {
213                 // If olditem is returned, nothing was added.
214                 bool nothing_added = (item1 == olditem);
215                 
216                 // If something else is returned, part of the item was left unadded.
217                 // Add the other part back to the source item
218                 list_from->addItem(from_i, item1);
219
220                 // If olditem is returned, nothing was added.
221                 // Swap the items
222                 if(nothing_added)
223                 {
224                         // Take item from source list
225                         item1 = list_from->changeItem(from_i, NULL);
226                         // Adding was not possible, swap the items.
227                         InventoryItem *item2 = list_to->changeItem(to_i, item1);
228                         // Put item from destination list to the source list
229                         list_from->changeItem(from_i, item2);
230                 }
231         }
232
233         mgr->inventoryModified(c, from_inv);
234         if(from_inv != to_inv)
235                 mgr->inventoryModified(c, to_inv);
236         
237         infostream<<"IMoveAction::apply(): moved at "
238                         <<"["<<describeC(c)<<"]"
239                         <<" from inv=\""<<from_inv<<"\""
240                         <<" list=\""<<from_list<<"\""
241                         <<" i="<<from_i
242                         <<" to inv=\""<<to_inv<<"\""
243                         <<" list=\""<<to_list<<"\""
244                         <<" i="<<to_i
245                         <<std::endl;
246 }
247
248 IDropAction::IDropAction(std::istream &is)
249 {
250         std::string ts;
251
252         std::getline(is, ts, ' ');
253         count = stoi(ts);
254
255         std::getline(is, from_inv, ' ');
256
257         std::getline(is, from_list, ' ');
258
259         std::getline(is, ts, ' ');
260         from_i = stoi(ts);
261 }
262
263 void IDropAction::apply(InventoryContext *c, InventoryManager *mgr,
264                 ServerEnvironment *env)
265 {
266         if(c->current_player == NULL){
267                 infostream<<"IDropAction::apply(): FAIL: current_player is NULL"<<std::endl;
268                 return;
269         }
270
271         // Do NOT cast directly to ServerActiveObject*, it breaks
272         // because of multiple inheritance.
273         ServerActiveObject *dropper =
274                 static_cast<ServerActiveObject*>(
275                 static_cast<ServerRemotePlayer*>(
276                         c->current_player
277                 ));
278
279         Inventory *inv_from = mgr->getInventory(c, from_inv);
280         
281         if(!inv_from){
282                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
283                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""<<std::endl;
284                 return;
285         }
286
287         InventoryList *list_from = inv_from->getList(from_list);
288
289         /*
290                 If a list doesn't exist or the source item doesn't exist
291         */
292         if(!list_from){
293                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
294                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
295                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
296                 return;
297         }
298         InventoryItem *item = list_from->getItem(from_i);
299         if(item == NULL)
300         {
301                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
302                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
303                                 <<", from_list=\""<<from_list<<"\""
304                                 <<" from_i="<<from_i<<std::endl;
305                 return;
306         }
307
308         v3f pos = dropper->getBasePosition();
309         pos.Y += 0.5*BS;
310
311         s16 count2 = count;
312         if(count2 == 0)
313                 count2 = -1;
314
315         /*
316                 Drop the item
317         */
318         bool remove = item->dropOrPlace(env, dropper, pos, false, count2);
319         if(remove)
320                 list_from->deleteItem(from_i);
321
322         mgr->inventoryModified(c, from_inv);
323
324         infostream<<"IDropAction::apply(): dropped "
325                         <<"["<<describeC(c)<<"]"
326                         <<" from inv=\""<<from_inv<<"\""
327                         <<" list=\""<<from_list<<"\""
328                         <<" i="<<from_i
329                         <<std::endl;
330 }
331
332 /*
333         Craft checking system
334 */
335
336 bool ItemSpec::checkItem(const InventoryItem *item) const
337 {
338         if(type == ITEM_NONE)
339         {
340                 // Has to be no item
341                 if(item != NULL)
342                         return false;
343                 return true;
344         }
345         
346         // There should be an item
347         if(item == NULL)
348                 return false;
349
350         std::string itemname = item->getName();
351
352         if(type == ITEM_MATERIAL)
353         {
354                 if(itemname != "MaterialItem")
355                         return false;
356                 MaterialItem *mitem = (MaterialItem*)item;
357                 if(num != 65535){
358                         if(mitem->getMaterial() != num)
359                                 return false;
360                 } else {
361                         if(mitem->getNodeName() != name)
362                                 return false;
363                 }
364         }
365         else if(type == ITEM_CRAFT)
366         {
367                 if(itemname != "CraftItem")
368                         return false;
369                 CraftItem *mitem = (CraftItem*)item;
370                 if(mitem->getSubName() != name)
371                         return false;
372         }
373         else if(type == ITEM_TOOL)
374         {
375                 // Not supported yet
376                 assert(0);
377         }
378         else if(type == ITEM_MBO)
379         {
380                 // Not supported yet
381                 assert(0);
382         }
383         else
384         {
385                 // Not supported yet
386                 assert(0);
387         }
388         return true;
389 }
390
391 bool checkItemCombination(InventoryItem const * const *items, const ItemSpec *specs)
392 {
393         u16 items_min_x = 100;
394         u16 items_max_x = 100;
395         u16 items_min_y = 100;
396         u16 items_max_y = 100;
397         for(u16 y=0; y<3; y++)
398         for(u16 x=0; x<3; x++)
399         {
400                 if(items[y*3 + x] == NULL)
401                         continue;
402                 if(items_min_x == 100 || x < items_min_x)
403                         items_min_x = x;
404                 if(items_min_y == 100 || y < items_min_y)
405                         items_min_y = y;
406                 if(items_max_x == 100 || x > items_max_x)
407                         items_max_x = x;
408                 if(items_max_y == 100 || y > items_max_y)
409                         items_max_y = y;
410         }
411         // No items at all, just return false
412         if(items_min_x == 100)
413                 return false;
414         
415         u16 items_w = items_max_x - items_min_x + 1;
416         u16 items_h = items_max_y - items_min_y + 1;
417
418         u16 specs_min_x = 100;
419         u16 specs_max_x = 100;
420         u16 specs_min_y = 100;
421         u16 specs_max_y = 100;
422         for(u16 y=0; y<3; y++)
423         for(u16 x=0; x<3; x++)
424         {
425                 if(specs[y*3 + x].type == ITEM_NONE)
426                         continue;
427                 if(specs_min_x == 100 || x < specs_min_x)
428                         specs_min_x = x;
429                 if(specs_min_y == 100 || y < specs_min_y)
430                         specs_min_y = y;
431                 if(specs_max_x == 100 || x > specs_max_x)
432                         specs_max_x = x;
433                 if(specs_max_y == 100 || y > specs_max_y)
434                         specs_max_y = y;
435         }
436         // No specs at all, just return false
437         if(specs_min_x == 100)
438                 return false;
439
440         u16 specs_w = specs_max_x - specs_min_x + 1;
441         u16 specs_h = specs_max_y - specs_min_y + 1;
442
443         // Different sizes
444         if(items_w != specs_w || items_h != specs_h)
445                 return false;
446
447         for(u16 y=0; y<specs_h; y++)
448         for(u16 x=0; x<specs_w; x++)
449         {
450                 u16 items_x = items_min_x + x;
451                 u16 items_y = items_min_y + y;
452                 u16 specs_x = specs_min_x + x;
453                 u16 specs_y = specs_min_y + y;
454                 const InventoryItem *item = items[items_y * 3 + items_x];
455                 const ItemSpec &spec = specs[specs_y * 3 + specs_x];
456
457                 if(spec.checkItem(item) == false)
458                         return false;
459         }
460
461         return true;
462 }
463
464 bool checkItemCombination(const InventoryItem * const * items,
465                 const InventoryItem * const * specs)
466 {
467         u16 items_min_x = 100;
468         u16 items_max_x = 100;
469         u16 items_min_y = 100;
470         u16 items_max_y = 100;
471         for(u16 y=0; y<3; y++)
472         for(u16 x=0; x<3; x++)
473         {
474                 if(items[y*3 + x] == NULL)
475                         continue;
476                 if(items_min_x == 100 || x < items_min_x)
477                         items_min_x = x;
478                 if(items_min_y == 100 || y < items_min_y)
479                         items_min_y = y;
480                 if(items_max_x == 100 || x > items_max_x)
481                         items_max_x = x;
482                 if(items_max_y == 100 || y > items_max_y)
483                         items_max_y = y;
484         }
485         // No items at all, just return false
486         if(items_min_x == 100)
487                 return false;
488         
489         u16 items_w = items_max_x - items_min_x + 1;
490         u16 items_h = items_max_y - items_min_y + 1;
491
492         u16 specs_min_x = 100;
493         u16 specs_max_x = 100;
494         u16 specs_min_y = 100;
495         u16 specs_max_y = 100;
496         for(u16 y=0; y<3; y++)
497         for(u16 x=0; x<3; x++)
498         {
499                 if(specs[y*3 + x] == NULL)
500                         continue;
501                 if(specs_min_x == 100 || x < specs_min_x)
502                         specs_min_x = x;
503                 if(specs_min_y == 100 || y < specs_min_y)
504                         specs_min_y = y;
505                 if(specs_max_x == 100 || x > specs_max_x)
506                         specs_max_x = x;
507                 if(specs_max_y == 100 || y > specs_max_y)
508                         specs_max_y = y;
509         }
510         // No specs at all, just return false
511         if(specs_min_x == 100)
512                 return false;
513
514         u16 specs_w = specs_max_x - specs_min_x + 1;
515         u16 specs_h = specs_max_y - specs_min_y + 1;
516
517         // Different sizes
518         if(items_w != specs_w || items_h != specs_h)
519                 return false;
520
521         for(u16 y=0; y<specs_h; y++)
522         for(u16 x=0; x<specs_w; x++)
523         {
524                 u16 items_x = items_min_x + x;
525                 u16 items_y = items_min_y + y;
526                 u16 specs_x = specs_min_x + x;
527                 u16 specs_y = specs_min_y + y;
528                 const InventoryItem *item = items[items_y * 3 + items_x];
529                 const InventoryItem *spec = specs[specs_y * 3 + specs_x];
530                 
531                 if(item == NULL && spec == NULL)
532                         continue;
533                 if(item == NULL && spec != NULL)
534                         return false;
535                 if(item != NULL && spec == NULL)
536                         return false;
537                 if(!spec->isSubsetOf(item))
538                         return false;
539         }
540
541         return true;
542 }
543
544