split scriptapi.cpp
[oweals/minetest.git] / src / scriptapi_inventory.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 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 "scriptapi.h"
21 #include "scriptapi_inventory.h"
22 #include "server.h"
23 #include "script.h"
24 #include "log.h"
25 #include "scriptapi_types.h"
26 #include "scriptapi_common.h"
27 #include "scriptapi_inventory.h"
28 #include "scriptapi_item.h"
29 #include "scriptapi_object.h"
30
31
32 /*
33         InvRef
34 */
35 InvRef* InvRef::checkobject(lua_State *L, int narg)
36 {
37         luaL_checktype(L, narg, LUA_TUSERDATA);
38         void *ud = luaL_checkudata(L, narg, className);
39         if(!ud) luaL_typerror(L, narg, className);
40         return *(InvRef**)ud;  // unbox pointer
41 }
42
43 Inventory* InvRef::getinv(lua_State *L, InvRef *ref)
44 {
45         return get_server(L)->getInventory(ref->m_loc);
46 }
47
48 InventoryList* InvRef::getlist(lua_State *L, InvRef *ref,
49                 const char *listname)
50 {
51         Inventory *inv = getinv(L, ref);
52         if(!inv)
53                 return NULL;
54         return inv->getList(listname);
55 }
56
57 void InvRef::reportInventoryChange(lua_State *L, InvRef *ref)
58 {
59         // Inform other things that the inventory has changed
60         get_server(L)->setInventoryModified(ref->m_loc);
61 }
62
63 // Exported functions
64
65 // garbage collector
66 int InvRef::gc_object(lua_State *L) {
67         InvRef *o = *(InvRef **)(lua_touserdata(L, 1));
68         delete o;
69         return 0;
70 }
71
72 // is_empty(self, listname) -> true/false
73 int InvRef::l_is_empty(lua_State *L)
74 {
75         InvRef *ref = checkobject(L, 1);
76         const char *listname = luaL_checkstring(L, 2);
77         InventoryList *list = getlist(L, ref, listname);
78         if(list && list->getUsedSlots() > 0){
79                 lua_pushboolean(L, false);
80         } else {
81                 lua_pushboolean(L, true);
82         }
83         return 1;
84 }
85
86 // get_size(self, listname)
87 int InvRef::l_get_size(lua_State *L)
88 {
89         InvRef *ref = checkobject(L, 1);
90         const char *listname = luaL_checkstring(L, 2);
91         InventoryList *list = getlist(L, ref, listname);
92         if(list){
93                 lua_pushinteger(L, list->getSize());
94         } else {
95                 lua_pushinteger(L, 0);
96         }
97         return 1;
98 }
99
100 // get_width(self, listname)
101 int InvRef::l_get_width(lua_State *L)
102 {
103         InvRef *ref = checkobject(L, 1);
104         const char *listname = luaL_checkstring(L, 2);
105         InventoryList *list = getlist(L, ref, listname);
106         if(list){
107                 lua_pushinteger(L, list->getWidth());
108         } else {
109                 lua_pushinteger(L, 0);
110         }
111         return 1;
112 }
113
114 // set_size(self, listname, size)
115 int InvRef::l_set_size(lua_State *L)
116 {
117         InvRef *ref = checkobject(L, 1);
118         const char *listname = luaL_checkstring(L, 2);
119         int newsize = luaL_checknumber(L, 3);
120         Inventory *inv = getinv(L, ref);
121         if(newsize == 0){
122                 inv->deleteList(listname);
123                 reportInventoryChange(L, ref);
124                 return 0;
125         }
126         InventoryList *list = inv->getList(listname);
127         if(list){
128                 list->setSize(newsize);
129         } else {
130                 list = inv->addList(listname, newsize);
131         }
132         reportInventoryChange(L, ref);
133         return 0;
134 }
135
136 // set_width(self, listname, size)
137 int InvRef::l_set_width(lua_State *L)
138 {
139         InvRef *ref = checkobject(L, 1);
140         const char *listname = luaL_checkstring(L, 2);
141         int newwidth = luaL_checknumber(L, 3);
142         Inventory *inv = getinv(L, ref);
143         InventoryList *list = inv->getList(listname);
144         if(list){
145                 list->setWidth(newwidth);
146         } else {
147                 return 0;
148         }
149         reportInventoryChange(L, ref);
150         return 0;
151 }
152
153 // get_stack(self, listname, i) -> itemstack
154 int InvRef::l_get_stack(lua_State *L)
155 {
156         InvRef *ref = checkobject(L, 1);
157         const char *listname = luaL_checkstring(L, 2);
158         int i = luaL_checknumber(L, 3) - 1;
159         InventoryList *list = getlist(L, ref, listname);
160         ItemStack item;
161         if(list != NULL && i >= 0 && i < (int) list->getSize())
162                 item = list->getItem(i);
163         LuaItemStack::create(L, item);
164         return 1;
165 }
166
167 // set_stack(self, listname, i, stack) -> true/false
168 int InvRef::l_set_stack(lua_State *L)
169 {
170         InvRef *ref = checkobject(L, 1);
171         const char *listname = luaL_checkstring(L, 2);
172         int i = luaL_checknumber(L, 3) - 1;
173         ItemStack newitem = read_item(L, 4);
174         InventoryList *list = getlist(L, ref, listname);
175         if(list != NULL && i >= 0 && i < (int) list->getSize()){
176                 list->changeItem(i, newitem);
177                 reportInventoryChange(L, ref);
178                 lua_pushboolean(L, true);
179         } else {
180                 lua_pushboolean(L, false);
181         }
182         return 1;
183 }
184
185 // get_list(self, listname) -> list or nil
186 int InvRef::l_get_list(lua_State *L)
187 {
188         InvRef *ref = checkobject(L, 1);
189         const char *listname = luaL_checkstring(L, 2);
190         Inventory *inv = getinv(L, ref);
191         inventory_get_list_to_lua(inv, listname, L);
192         return 1;
193 }
194
195 // set_list(self, listname, list)
196 int InvRef::l_set_list(lua_State *L)
197 {
198         InvRef *ref = checkobject(L, 1);
199         const char *listname = luaL_checkstring(L, 2);
200         Inventory *inv = getinv(L, ref);
201         InventoryList *list = inv->getList(listname);
202         if(list)
203                 inventory_set_list_from_lua(inv, listname, L, 3,
204                                 list->getSize());
205         else
206                 inventory_set_list_from_lua(inv, listname, L, 3);
207         reportInventoryChange(L, ref);
208         return 0;
209 }
210
211 // add_item(self, listname, itemstack or itemstring or table or nil) -> itemstack
212 // Returns the leftover stack
213 int InvRef::l_add_item(lua_State *L)
214 {
215         InvRef *ref = checkobject(L, 1);
216         const char *listname = luaL_checkstring(L, 2);
217         ItemStack item = read_item(L, 3);
218         InventoryList *list = getlist(L, ref, listname);
219         if(list){
220                 ItemStack leftover = list->addItem(item);
221                 if(leftover.count != item.count)
222                         reportInventoryChange(L, ref);
223                 LuaItemStack::create(L, leftover);
224         } else {
225                 LuaItemStack::create(L, item);
226         }
227         return 1;
228 }
229
230 // room_for_item(self, listname, itemstack or itemstring or table or nil) -> true/false
231 // Returns true if the item completely fits into the list
232 int InvRef::l_room_for_item(lua_State *L)
233 {
234         InvRef *ref = checkobject(L, 1);
235         const char *listname = luaL_checkstring(L, 2);
236         ItemStack item = read_item(L, 3);
237         InventoryList *list = getlist(L, ref, listname);
238         if(list){
239                 lua_pushboolean(L, list->roomForItem(item));
240         } else {
241                 lua_pushboolean(L, false);
242         }
243         return 1;
244 }
245
246 // contains_item(self, listname, itemstack or itemstring or table or nil) -> true/false
247 // Returns true if the list contains the given count of the given item name
248 int InvRef::l_contains_item(lua_State *L)
249 {
250         InvRef *ref = checkobject(L, 1);
251         const char *listname = luaL_checkstring(L, 2);
252         ItemStack item = read_item(L, 3);
253         InventoryList *list = getlist(L, ref, listname);
254         if(list){
255                 lua_pushboolean(L, list->containsItem(item));
256         } else {
257                 lua_pushboolean(L, false);
258         }
259         return 1;
260 }
261
262 // remove_item(self, listname, itemstack or itemstring or table or nil) -> itemstack
263 // Returns the items that were actually removed
264 int InvRef::l_remove_item(lua_State *L)
265 {
266         InvRef *ref = checkobject(L, 1);
267         const char *listname = luaL_checkstring(L, 2);
268         ItemStack item = read_item(L, 3);
269         InventoryList *list = getlist(L, ref, listname);
270         if(list){
271                 ItemStack removed = list->removeItem(item);
272                 if(!removed.empty())
273                         reportInventoryChange(L, ref);
274                 LuaItemStack::create(L, removed);
275         } else {
276                 LuaItemStack::create(L, ItemStack());
277         }
278         return 1;
279 }
280
281 // get_location() -> location (like minetest.get_inventory(location))
282 int InvRef::l_get_location(lua_State *L)
283 {
284         InvRef *ref = checkobject(L, 1);
285         const InventoryLocation &loc = ref->m_loc;
286         switch(loc.type){
287         case InventoryLocation::PLAYER:
288                 lua_newtable(L);
289                 lua_pushstring(L, "player");
290                 lua_setfield(L, -2, "type");
291                 lua_pushstring(L, loc.name.c_str());
292                 lua_setfield(L, -2, "name");
293                 return 1;
294         case InventoryLocation::NODEMETA:
295                 lua_newtable(L);
296                 lua_pushstring(L, "nodemeta");
297                 lua_setfield(L, -2, "type");
298                 push_v3s16(L, loc.p);
299                 lua_setfield(L, -2, "name");
300                 return 1;
301         case InventoryLocation::DETACHED:
302                 lua_newtable(L);
303                 lua_pushstring(L, "detached");
304                 lua_setfield(L, -2, "type");
305                 lua_pushstring(L, loc.name.c_str());
306                 lua_setfield(L, -2, "name");
307                 return 1;
308         case InventoryLocation::UNDEFINED:
309         case InventoryLocation::CURRENT_PLAYER:
310                 break;
311         }
312         lua_newtable(L);
313         lua_pushstring(L, "undefined");
314         lua_setfield(L, -2, "type");
315         return 1;
316 }
317
318
319 InvRef::InvRef(const InventoryLocation &loc):
320         m_loc(loc)
321 {
322 }
323
324 InvRef::~InvRef()
325 {
326 }
327
328 // Creates an InvRef and leaves it on top of stack
329 // Not callable from Lua; all references are created on the C side.
330 void InvRef::create(lua_State *L, const InventoryLocation &loc)
331 {
332         InvRef *o = new InvRef(loc);
333         *(void **)(lua_newuserdata(L, sizeof(void *))) = o;
334         luaL_getmetatable(L, className);
335         lua_setmetatable(L, -2);
336 }
337 void InvRef::createPlayer(lua_State *L, Player *player)
338 {
339         InventoryLocation loc;
340         loc.setPlayer(player->getName());
341         create(L, loc);
342 }
343 void InvRef::createNodeMeta(lua_State *L, v3s16 p)
344 {
345         InventoryLocation loc;
346         loc.setNodeMeta(p);
347         create(L, loc);
348 }
349
350 void InvRef::Register(lua_State *L)
351 {
352         lua_newtable(L);
353         int methodtable = lua_gettop(L);
354         luaL_newmetatable(L, className);
355         int metatable = lua_gettop(L);
356
357         lua_pushliteral(L, "__metatable");
358         lua_pushvalue(L, methodtable);
359         lua_settable(L, metatable);  // hide metatable from Lua getmetatable()
360
361         lua_pushliteral(L, "__index");
362         lua_pushvalue(L, methodtable);
363         lua_settable(L, metatable);
364
365         lua_pushliteral(L, "__gc");
366         lua_pushcfunction(L, gc_object);
367         lua_settable(L, metatable);
368
369         lua_pop(L, 1);  // drop metatable
370
371         luaL_openlib(L, 0, methods, 0);  // fill methodtable
372         lua_pop(L, 1);  // drop methodtable
373
374         // Cannot be created from Lua
375         //lua_register(L, className, create_object);
376 }
377
378 const char InvRef::className[] = "InvRef";
379 const luaL_reg InvRef::methods[] = {
380         luamethod(InvRef, is_empty),
381         luamethod(InvRef, get_size),
382         luamethod(InvRef, set_size),
383         luamethod(InvRef, get_width),
384         luamethod(InvRef, set_width),
385         luamethod(InvRef, get_stack),
386         luamethod(InvRef, set_stack),
387         luamethod(InvRef, get_list),
388         luamethod(InvRef, set_list),
389         luamethod(InvRef, add_item),
390         luamethod(InvRef, room_for_item),
391         luamethod(InvRef, contains_item),
392         luamethod(InvRef, remove_item),
393         luamethod(InvRef, get_location),
394         {0,0}
395 };
396
397 void inventory_get_list_to_lua(Inventory *inv, const char *name,
398                 lua_State *L)
399 {
400         InventoryList *invlist = inv->getList(name);
401         if(invlist == NULL){
402                 lua_pushnil(L);
403                 return;
404         }
405         std::vector<ItemStack> items;
406         for(u32 i=0; i<invlist->getSize(); i++)
407                 items.push_back(invlist->getItem(i));
408         push_items(L, items);
409 }
410
411 void inventory_set_list_from_lua(Inventory *inv, const char *name,
412                 lua_State *L, int tableindex, int forcesize)
413 {
414         if(tableindex < 0)
415                 tableindex = lua_gettop(L) + 1 + tableindex;
416         // If nil, delete list
417         if(lua_isnil(L, tableindex)){
418                 inv->deleteList(name);
419                 return;
420         }
421         // Otherwise set list
422         std::vector<ItemStack> items = read_items(L, tableindex);
423         int listsize = (forcesize != -1) ? forcesize : items.size();
424         InventoryList *invlist = inv->addList(name, listsize);
425         int index = 0;
426         for(std::vector<ItemStack>::const_iterator
427                         i = items.begin(); i != items.end(); i++){
428                 if(forcesize != -1 && index == forcesize)
429                         break;
430                 invlist->changeItem(index, *i);
431                 index++;
432         }
433         while(forcesize != -1 && index < forcesize){
434                 invlist->deleteItem(index);
435                 index++;
436         }
437 }
438
439 // get_inventory(location)
440 int l_get_inventory(lua_State *L)
441 {
442         InventoryLocation loc;
443
444         std::string type = checkstringfield(L, 1, "type");
445         if(type == "player"){
446                 std::string name = checkstringfield(L, 1, "name");
447                 loc.setPlayer(name);
448         } else if(type == "node"){
449                 lua_getfield(L, 1, "pos");
450                 v3s16 pos = check_v3s16(L, -1);
451                 loc.setNodeMeta(pos);
452         } else if(type == "detached"){
453                 std::string name = checkstringfield(L, 1, "name");
454                 loc.setDetached(name);
455         }
456
457         if(get_server(L)->getInventory(loc) != NULL)
458                 InvRef::create(L, loc);
459         else
460                 lua_pushnil(L);
461         return 1;
462 }
463
464 /*
465         Detached inventory callbacks
466 */
467
468 // Retrieves minetest.detached_inventories[name][callbackname]
469 // If that is nil or on error, return false and stack is unchanged
470 // If that is a function, returns true and pushes the
471 // function onto the stack
472 static bool get_detached_inventory_callback(lua_State *L,
473                 const std::string &name, const char *callbackname)
474 {
475         lua_getglobal(L, "minetest");
476         lua_getfield(L, -1, "detached_inventories");
477         lua_remove(L, -2);
478         luaL_checktype(L, -1, LUA_TTABLE);
479         lua_getfield(L, -1, name.c_str());
480         lua_remove(L, -2);
481         // Should be a table
482         if(lua_type(L, -1) != LUA_TTABLE)
483         {
484                 errorstream<<"Item \""<<name<<"\" not defined"<<std::endl;
485                 lua_pop(L, 1);
486                 return false;
487         }
488         lua_getfield(L, -1, callbackname);
489         lua_remove(L, -2);
490         // Should be a function or nil
491         if(lua_type(L, -1) == LUA_TFUNCTION)
492         {
493                 return true;
494         }
495         else if(lua_isnil(L, -1))
496         {
497                 lua_pop(L, 1);
498                 return false;
499         }
500         else
501         {
502                 errorstream<<"Detached inventory \""<<name<<"\" callback \""
503                         <<callbackname<<"\" is not a function"<<std::endl;
504                 lua_pop(L, 1);
505                 return false;
506         }
507 }
508
509 // Return number of accepted items to be moved
510 int scriptapi_detached_inventory_allow_move(lua_State *L,
511                 const std::string &name,
512                 const std::string &from_list, int from_index,
513                 const std::string &to_list, int to_index,
514                 int count, ServerActiveObject *player)
515 {
516         realitycheck(L);
517         assert(lua_checkstack(L, 20));
518         StackUnroller stack_unroller(L);
519
520         // Push callback function on stack
521         if(!get_detached_inventory_callback(L, name, "allow_move"))
522                 return count;
523
524         // function(inv, from_list, from_index, to_list, to_index, count, player)
525         // inv
526         InventoryLocation loc;
527         loc.setDetached(name);
528         InvRef::create(L, loc);
529         // from_list
530         lua_pushstring(L, from_list.c_str());
531         // from_index
532         lua_pushinteger(L, from_index + 1);
533         // to_list
534         lua_pushstring(L, to_list.c_str());
535         // to_index
536         lua_pushinteger(L, to_index + 1);
537         // count
538         lua_pushinteger(L, count);
539         // player
540         objectref_get_or_create(L, player);
541         if(lua_pcall(L, 7, 1, 0))
542                 script_error(L, "error: %s", lua_tostring(L, -1));
543         if(!lua_isnumber(L, -1))
544                 throw LuaError(L, "allow_move should return a number");
545         return luaL_checkinteger(L, -1);
546 }
547
548 // Return number of accepted items to be put
549 int scriptapi_detached_inventory_allow_put(lua_State *L,
550                 const std::string &name,
551                 const std::string &listname, int index, ItemStack &stack,
552                 ServerActiveObject *player)
553 {
554         realitycheck(L);
555         assert(lua_checkstack(L, 20));
556         StackUnroller stack_unroller(L);
557
558         // Push callback function on stack
559         if(!get_detached_inventory_callback(L, name, "allow_put"))
560                 return stack.count; // All will be accepted
561
562         // Call function(inv, listname, index, stack, player)
563         // inv
564         InventoryLocation loc;
565         loc.setDetached(name);
566         InvRef::create(L, loc);
567         // listname
568         lua_pushstring(L, listname.c_str());
569         // index
570         lua_pushinteger(L, index + 1);
571         // stack
572         LuaItemStack::create(L, stack);
573         // player
574         objectref_get_or_create(L, player);
575         if(lua_pcall(L, 5, 1, 0))
576                 script_error(L, "error: %s", lua_tostring(L, -1));
577         if(!lua_isnumber(L, -1))
578                 throw LuaError(L, "allow_put should return a number");
579         return luaL_checkinteger(L, -1);
580 }
581
582 // Return number of accepted items to be taken
583 int scriptapi_detached_inventory_allow_take(lua_State *L,
584                 const std::string &name,
585                 const std::string &listname, int index, ItemStack &stack,
586                 ServerActiveObject *player)
587 {
588         realitycheck(L);
589         assert(lua_checkstack(L, 20));
590         StackUnroller stack_unroller(L);
591
592         // Push callback function on stack
593         if(!get_detached_inventory_callback(L, name, "allow_take"))
594                 return stack.count; // All will be accepted
595
596         // Call function(inv, listname, index, stack, player)
597         // inv
598         InventoryLocation loc;
599         loc.setDetached(name);
600         InvRef::create(L, loc);
601         // listname
602         lua_pushstring(L, listname.c_str());
603         // index
604         lua_pushinteger(L, index + 1);
605         // stack
606         LuaItemStack::create(L, stack);
607         // player
608         objectref_get_or_create(L, player);
609         if(lua_pcall(L, 5, 1, 0))
610                 script_error(L, "error: %s", lua_tostring(L, -1));
611         if(!lua_isnumber(L, -1))
612                 throw LuaError(L, "allow_take should return a number");
613         return luaL_checkinteger(L, -1);
614 }
615
616 // Report moved items
617 void scriptapi_detached_inventory_on_move(lua_State *L,
618                 const std::string &name,
619                 const std::string &from_list, int from_index,
620                 const std::string &to_list, int to_index,
621                 int count, ServerActiveObject *player)
622 {
623         realitycheck(L);
624         assert(lua_checkstack(L, 20));
625         StackUnroller stack_unroller(L);
626
627         // Push callback function on stack
628         if(!get_detached_inventory_callback(L, name, "on_move"))
629                 return;
630
631         // function(inv, from_list, from_index, to_list, to_index, count, player)
632         // inv
633         InventoryLocation loc;
634         loc.setDetached(name);
635         InvRef::create(L, loc);
636         // from_list
637         lua_pushstring(L, from_list.c_str());
638         // from_index
639         lua_pushinteger(L, from_index + 1);
640         // to_list
641         lua_pushstring(L, to_list.c_str());
642         // to_index
643         lua_pushinteger(L, to_index + 1);
644         // count
645         lua_pushinteger(L, count);
646         // player
647         objectref_get_or_create(L, player);
648         if(lua_pcall(L, 7, 0, 0))
649                 script_error(L, "error: %s", lua_tostring(L, -1));
650 }
651
652 // Report put items
653 void scriptapi_detached_inventory_on_put(lua_State *L,
654                 const std::string &name,
655                 const std::string &listname, int index, ItemStack &stack,
656                 ServerActiveObject *player)
657 {
658         realitycheck(L);
659         assert(lua_checkstack(L, 20));
660         StackUnroller stack_unroller(L);
661
662         // Push callback function on stack
663         if(!get_detached_inventory_callback(L, name, "on_put"))
664                 return;
665
666         // Call function(inv, listname, index, stack, player)
667         // inv
668         InventoryLocation loc;
669         loc.setDetached(name);
670         InvRef::create(L, loc);
671         // listname
672         lua_pushstring(L, listname.c_str());
673         // index
674         lua_pushinteger(L, index + 1);
675         // stack
676         LuaItemStack::create(L, stack);
677         // player
678         objectref_get_or_create(L, player);
679         if(lua_pcall(L, 5, 0, 0))
680                 script_error(L, "error: %s", lua_tostring(L, -1));
681 }
682
683 // Report taken items
684 void scriptapi_detached_inventory_on_take(lua_State *L,
685                 const std::string &name,
686                 const std::string &listname, int index, ItemStack &stack,
687                 ServerActiveObject *player)
688 {
689         realitycheck(L);
690         assert(lua_checkstack(L, 20));
691         StackUnroller stack_unroller(L);
692
693         // Push callback function on stack
694         if(!get_detached_inventory_callback(L, name, "on_take"))
695                 return;
696
697         // Call function(inv, listname, index, stack, player)
698         // inv
699         InventoryLocation loc;
700         loc.setDetached(name);
701         InvRef::create(L, loc);
702         // listname
703         lua_pushstring(L, listname.c_str());
704         // index
705         lua_pushinteger(L, index + 1);
706         // stack
707         LuaItemStack::create(L, stack);
708         // player
709         objectref_get_or_create(L, player);
710         if(lua_pcall(L, 5, 0, 0))
711                 script_error(L, "error: %s", lua_tostring(L, -1));
712 }
713
714 // create_detached_inventory_raw(name)
715 int l_create_detached_inventory_raw(lua_State *L)
716 {
717         const char *name = luaL_checkstring(L, 1);
718         if(get_server(L)->createDetachedInventory(name) != NULL){
719                 InventoryLocation loc;
720                 loc.setDetached(name);
721                 InvRef::create(L, loc);
722         }else{
723                 lua_pushnil(L);
724         }
725         return 1;
726 }