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