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 "lua_api/l_inventory.h"
21 #include "lua_api/l_internal.h"
22 #include "lua_api/l_item.h"
23 #include "common/c_converter.h"
24 #include "common/c_content.h"
25 #include "server.h"
26 #include "server/serverinventorymgr.h"
27 #include "remoteplayer.h"
28 
29 /*
30 	InvRef
31 */
checkobject(lua_State * L,int narg)32 InvRef* InvRef::checkobject(lua_State *L, int narg)
33 {
34 	luaL_checktype(L, narg, LUA_TUSERDATA);
35 	void *ud = luaL_checkudata(L, narg, className);
36 	if(!ud) luaL_typerror(L, narg, className);
37 	return *(InvRef**)ud;  // unbox pointer
38 }
39 
getinv(lua_State * L,InvRef * ref)40 Inventory* InvRef::getinv(lua_State *L, InvRef *ref)
41 {
42 	return getServerInventoryMgr(L)->getInventory(ref->m_loc);
43 }
44 
getlist(lua_State * L,InvRef * ref,const char * listname)45 InventoryList* InvRef::getlist(lua_State *L, InvRef *ref,
46 		const char *listname)
47 {
48 	NO_MAP_LOCK_REQUIRED;
49 	Inventory *inv = getinv(L, ref);
50 	if(!inv)
51 		return NULL;
52 	return inv->getList(listname);
53 }
54 
reportInventoryChange(lua_State * L,InvRef * ref)55 void InvRef::reportInventoryChange(lua_State *L, InvRef *ref)
56 {
57 	// Inform other things that the inventory has changed
58 	getServerInventoryMgr(L)->setInventoryModified(ref->m_loc);
59 }
60 
61 // Exported functions
62 
63 // garbage collector
gc_object(lua_State * L)64 int InvRef::gc_object(lua_State *L) {
65 	InvRef *o = *(InvRef **)(lua_touserdata(L, 1));
66 	delete o;
67 	return 0;
68 }
69 
70 // is_empty(self, listname) -> true/false
l_is_empty(lua_State * L)71 int InvRef::l_is_empty(lua_State *L)
72 {
73 	NO_MAP_LOCK_REQUIRED;
74 	InvRef *ref = checkobject(L, 1);
75 	const char *listname = luaL_checkstring(L, 2);
76 	InventoryList *list = getlist(L, ref, listname);
77 	if(list && list->getUsedSlots() > 0){
78 		lua_pushboolean(L, false);
79 	} else {
80 		lua_pushboolean(L, true);
81 	}
82 	return 1;
83 }
84 
85 // get_size(self, listname)
l_get_size(lua_State * L)86 int InvRef::l_get_size(lua_State *L)
87 {
88 	NO_MAP_LOCK_REQUIRED;
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)
l_get_width(lua_State * L)101 int InvRef::l_get_width(lua_State *L)
102 {
103 	NO_MAP_LOCK_REQUIRED;
104 	InvRef *ref = checkobject(L, 1);
105 	const char *listname = luaL_checkstring(L, 2);
106 	InventoryList *list = getlist(L, ref, listname);
107 	if(list){
108 		lua_pushinteger(L, list->getWidth());
109 	} else {
110 		lua_pushinteger(L, 0);
111 	}
112 	return 1;
113 }
114 
115 // set_size(self, listname, size)
l_set_size(lua_State * L)116 int InvRef::l_set_size(lua_State *L)
117 {
118 	NO_MAP_LOCK_REQUIRED;
119 	InvRef *ref = checkobject(L, 1);
120 	const char *listname = luaL_checkstring(L, 2);
121 
122 	int newsize = luaL_checknumber(L, 3);
123 	if (newsize < 0) {
124 		lua_pushboolean(L, false);
125 		return 1;
126 	}
127 
128 	Inventory *inv = getinv(L, ref);
129 	if(inv == NULL){
130 		lua_pushboolean(L, false);
131 		return 1;
132 	}
133 	if(newsize == 0){
134 		inv->deleteList(listname);
135 		reportInventoryChange(L, ref);
136 		lua_pushboolean(L, true);
137 		return 1;
138 	}
139 	InventoryList *list = inv->getList(listname);
140 	if(list){
141 		list->setSize(newsize);
142 	} else {
143 		list = inv->addList(listname, newsize);
144 		if (!list)
145 		{
146 			lua_pushboolean(L, false);
147 			return 1;
148 		}
149 	}
150 	reportInventoryChange(L, ref);
151 	lua_pushboolean(L, true);
152 	return 1;
153 }
154 
155 // set_width(self, listname, size)
l_set_width(lua_State * L)156 int InvRef::l_set_width(lua_State *L)
157 {
158 	NO_MAP_LOCK_REQUIRED;
159 	InvRef *ref = checkobject(L, 1);
160 	const char *listname = luaL_checkstring(L, 2);
161 	int newwidth = luaL_checknumber(L, 3);
162 	Inventory *inv = getinv(L, ref);
163 	if(inv == NULL){
164 		return 0;
165 	}
166 	InventoryList *list = inv->getList(listname);
167 	if(list){
168 		list->setWidth(newwidth);
169 	} else {
170 		return 0;
171 	}
172 	reportInventoryChange(L, ref);
173 	return 0;
174 }
175 
176 // get_stack(self, listname, i) -> itemstack
l_get_stack(lua_State * L)177 int InvRef::l_get_stack(lua_State *L)
178 {
179 	NO_MAP_LOCK_REQUIRED;
180 	InvRef *ref = checkobject(L, 1);
181 	const char *listname = luaL_checkstring(L, 2);
182 	int i = luaL_checknumber(L, 3) - 1;
183 	InventoryList *list = getlist(L, ref, listname);
184 	ItemStack item;
185 	if(list != NULL && i >= 0 && i < (int) list->getSize())
186 		item = list->getItem(i);
187 	LuaItemStack::create(L, item);
188 	return 1;
189 }
190 
191 // set_stack(self, listname, i, stack) -> true/false
l_set_stack(lua_State * L)192 int InvRef::l_set_stack(lua_State *L)
193 {
194 	NO_MAP_LOCK_REQUIRED;
195 	InvRef *ref = checkobject(L, 1);
196 	const char *listname = luaL_checkstring(L, 2);
197 	int i = luaL_checknumber(L, 3) - 1;
198 	ItemStack newitem = read_item(L, 4, getServer(L)->idef());
199 	InventoryList *list = getlist(L, ref, listname);
200 	if(list != NULL && i >= 0 && i < (int) list->getSize()){
201 		list->changeItem(i, newitem);
202 		reportInventoryChange(L, ref);
203 		lua_pushboolean(L, true);
204 	} else {
205 		lua_pushboolean(L, false);
206 	}
207 	return 1;
208 }
209 
210 // get_list(self, listname) -> list or nil
l_get_list(lua_State * L)211 int InvRef::l_get_list(lua_State *L)
212 {
213 	NO_MAP_LOCK_REQUIRED;
214 	InvRef *ref = checkobject(L, 1);
215 	const char *listname = luaL_checkstring(L, 2);
216 	Inventory *inv = getinv(L, ref);
217 	if(inv){
218 		push_inventory_list(L, inv, listname);
219 	} else {
220 		lua_pushnil(L);
221 	}
222 	return 1;
223 }
224 
225 // set_list(self, listname, list)
l_set_list(lua_State * L)226 int InvRef::l_set_list(lua_State *L)
227 {
228 	NO_MAP_LOCK_REQUIRED;
229 	InvRef *ref = checkobject(L, 1);
230 	const char *listname = luaL_checkstring(L, 2);
231 	Inventory *inv = getinv(L, ref);
232 	if(inv == NULL){
233 		return 0;
234 	}
235 	InventoryList *list = inv->getList(listname);
236 	if(list)
237 		read_inventory_list(L, 3, inv, listname,
238 				getServer(L), list->getSize());
239 	else
240 		read_inventory_list(L, 3, inv, listname, getServer(L));
241 	reportInventoryChange(L, ref);
242 	return 0;
243 }
244 
245 // get_lists(self) -> list of InventoryLists
l_get_lists(lua_State * L)246 int InvRef::l_get_lists(lua_State *L)
247 {
248 	NO_MAP_LOCK_REQUIRED;
249 	InvRef *ref = checkobject(L, 1);
250 	Inventory *inv = getinv(L, ref);
251 	if (!inv) {
252 		return 0;
253 	}
254 	std::vector<const InventoryList*> lists = inv->getLists();
255 	std::vector<const InventoryList*>::iterator iter = lists.begin();
256 	lua_createtable(L, 0, lists.size());
257 	for (; iter != lists.end(); iter++) {
258 		const char* name = (*iter)->getName().c_str();
259 		lua_pushstring(L, name);
260 		push_inventory_list(L, inv, name);
261 		lua_rawset(L, -3);
262 	}
263 	return 1;
264 }
265 
266 // set_lists(self, lists)
l_set_lists(lua_State * L)267 int InvRef::l_set_lists(lua_State *L)
268 {
269 	NO_MAP_LOCK_REQUIRED;
270 	InvRef *ref = checkobject(L, 1);
271 	Inventory *inv = getinv(L, ref);
272 	if (!inv) {
273 		return 0;
274 	}
275 
276 	// Make a temporary inventory in case reading fails
277 	Inventory *tempInv(inv);
278 	tempInv->clear();
279 
280 	Server *server = getServer(L);
281 
282 	lua_pushnil(L);
283 	luaL_checktype(L, 2, LUA_TTABLE);
284 	while (lua_next(L, 2)) {
285 		const char *listname = lua_tostring(L, -2);
286 		read_inventory_list(L, -1, tempInv, listname, server);
287 		lua_pop(L, 1);
288 	}
289 	inv = tempInv;
290 	return 0;
291 }
292 
293 // add_item(self, listname, itemstack or itemstring or table or nil) -> itemstack
294 // Returns the leftover stack
l_add_item(lua_State * L)295 int InvRef::l_add_item(lua_State *L)
296 {
297 	NO_MAP_LOCK_REQUIRED;
298 	InvRef *ref = checkobject(L, 1);
299 	const char *listname = luaL_checkstring(L, 2);
300 	ItemStack item = read_item(L, 3, getServer(L)->idef());
301 	InventoryList *list = getlist(L, ref, listname);
302 	if(list){
303 		ItemStack leftover = list->addItem(item);
304 		if(leftover.count != item.count)
305 			reportInventoryChange(L, ref);
306 		LuaItemStack::create(L, leftover);
307 	} else {
308 		LuaItemStack::create(L, item);
309 	}
310 	return 1;
311 }
312 
313 // room_for_item(self, listname, itemstack or itemstring or table or nil) -> true/false
314 // Returns true if the item completely fits into the list
l_room_for_item(lua_State * L)315 int InvRef::l_room_for_item(lua_State *L)
316 {
317 	NO_MAP_LOCK_REQUIRED;
318 	InvRef *ref = checkobject(L, 1);
319 	const char *listname = luaL_checkstring(L, 2);
320 	ItemStack item = read_item(L, 3, getServer(L)->idef());
321 	InventoryList *list = getlist(L, ref, listname);
322 	if(list){
323 		lua_pushboolean(L, list->roomForItem(item));
324 	} else {
325 		lua_pushboolean(L, false);
326 	}
327 	return 1;
328 }
329 
330 // contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false
331 // Returns true if the list contains the given count of the given item
l_contains_item(lua_State * L)332 int InvRef::l_contains_item(lua_State *L)
333 {
334 	NO_MAP_LOCK_REQUIRED;
335 	InvRef *ref = checkobject(L, 1);
336 	const char *listname = luaL_checkstring(L, 2);
337 	ItemStack item = read_item(L, 3, getServer(L)->idef());
338 	InventoryList *list = getlist(L, ref, listname);
339 	bool match_meta = false;
340 	if (lua_isboolean(L, 4))
341 		match_meta = readParam<bool>(L, 4);
342 	if (list) {
343 		lua_pushboolean(L, list->containsItem(item, match_meta));
344 	} else {
345 		lua_pushboolean(L, false);
346 	}
347 	return 1;
348 }
349 
350 // remove_item(self, listname, itemstack or itemstring or table or nil) -> itemstack
351 // Returns the items that were actually removed
l_remove_item(lua_State * L)352 int InvRef::l_remove_item(lua_State *L)
353 {
354 	NO_MAP_LOCK_REQUIRED;
355 	InvRef *ref = checkobject(L, 1);
356 	const char *listname = luaL_checkstring(L, 2);
357 	ItemStack item = read_item(L, 3, getServer(L)->idef());
358 	InventoryList *list = getlist(L, ref, listname);
359 	if(list){
360 		ItemStack removed = list->removeItem(item);
361 		if(!removed.empty())
362 			reportInventoryChange(L, ref);
363 		LuaItemStack::create(L, removed);
364 	} else {
365 		LuaItemStack::create(L, ItemStack());
366 	}
367 	return 1;
368 }
369 
370 // get_location() -> location (like get_inventory(location))
l_get_location(lua_State * L)371 int InvRef::l_get_location(lua_State *L)
372 {
373 	NO_MAP_LOCK_REQUIRED;
374 	InvRef *ref = checkobject(L, 1);
375 	const InventoryLocation &loc = ref->m_loc;
376 	switch(loc.type){
377 	case InventoryLocation::PLAYER:
378 		lua_newtable(L);
379 		lua_pushstring(L, "player");
380 		lua_setfield(L, -2, "type");
381 		lua_pushstring(L, loc.name.c_str());
382 		lua_setfield(L, -2, "name");
383 		return 1;
384 	case InventoryLocation::NODEMETA:
385 		lua_newtable(L);
386 		lua_pushstring(L, "node");
387 		lua_setfield(L, -2, "type");
388 		push_v3s16(L, loc.p);
389 		lua_setfield(L, -2, "pos");
390 		return 1;
391 	case InventoryLocation::DETACHED:
392 		lua_newtable(L);
393 		lua_pushstring(L, "detached");
394 		lua_setfield(L, -2, "type");
395 		lua_pushstring(L, loc.name.c_str());
396 		lua_setfield(L, -2, "name");
397 		return 1;
398 	case InventoryLocation::UNDEFINED:
399 	case InventoryLocation::CURRENT_PLAYER:
400 		break;
401 	}
402 	lua_newtable(L);
403 	lua_pushstring(L, "undefined");
404 	lua_setfield(L, -2, "type");
405 	return 1;
406 }
407 
408 
InvRef(const InventoryLocation & loc)409 InvRef::InvRef(const InventoryLocation &loc):
410 	m_loc(loc)
411 {
412 }
413 
414 // Creates an InvRef and leaves it on top of stack
415 // Not callable from Lua; all references are created on the C side.
create(lua_State * L,const InventoryLocation & loc)416 void InvRef::create(lua_State *L, const InventoryLocation &loc)
417 {
418 	NO_MAP_LOCK_REQUIRED;
419 	InvRef *o = new InvRef(loc);
420 	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
421 	luaL_getmetatable(L, className);
422 	lua_setmetatable(L, -2);
423 }
createPlayer(lua_State * L,RemotePlayer * player)424 void InvRef::createPlayer(lua_State *L, RemotePlayer *player)
425 {
426 	NO_MAP_LOCK_REQUIRED;
427 	InventoryLocation loc;
428 	loc.setPlayer(player->getName());
429 	create(L, loc);
430 }
createNodeMeta(lua_State * L,v3s16 p)431 void InvRef::createNodeMeta(lua_State *L, v3s16 p)
432 {
433 	InventoryLocation loc;
434 	loc.setNodeMeta(p);
435 	create(L, loc);
436 }
437 
Register(lua_State * L)438 void InvRef::Register(lua_State *L)
439 {
440 	lua_newtable(L);
441 	int methodtable = lua_gettop(L);
442 	luaL_newmetatable(L, className);
443 	int metatable = lua_gettop(L);
444 
445 	lua_pushliteral(L, "__metatable");
446 	lua_pushvalue(L, methodtable);
447 	lua_settable(L, metatable);  // hide metatable from Lua getmetatable()
448 
449 	lua_pushliteral(L, "__index");
450 	lua_pushvalue(L, methodtable);
451 	lua_settable(L, metatable);
452 
453 	lua_pushliteral(L, "__gc");
454 	lua_pushcfunction(L, gc_object);
455 	lua_settable(L, metatable);
456 
457 	lua_pop(L, 1);  // drop metatable
458 
459 	luaL_openlib(L, 0, methods, 0);  // fill methodtable
460 	lua_pop(L, 1);  // drop methodtable
461 
462 	// Cannot be created from Lua
463 	//lua_register(L, className, create_object);
464 }
465 
466 const char InvRef::className[] = "InvRef";
467 const luaL_Reg InvRef::methods[] = {
468 	luamethod(InvRef, is_empty),
469 	luamethod(InvRef, get_size),
470 	luamethod(InvRef, set_size),
471 	luamethod(InvRef, get_width),
472 	luamethod(InvRef, set_width),
473 	luamethod(InvRef, get_stack),
474 	luamethod(InvRef, set_stack),
475 	luamethod(InvRef, get_list),
476 	luamethod(InvRef, set_list),
477 	luamethod(InvRef, get_lists),
478 	luamethod(InvRef, set_lists),
479 	luamethod(InvRef, add_item),
480 	luamethod(InvRef, room_for_item),
481 	luamethod(InvRef, contains_item),
482 	luamethod(InvRef, remove_item),
483 	luamethod(InvRef, get_location),
484 	{0,0}
485 };
486 
487 // get_inventory(location)
l_get_inventory(lua_State * L)488 int ModApiInventory::l_get_inventory(lua_State *L)
489 {
490 	InventoryLocation loc;
491 
492 	lua_getfield(L, 1, "type");
493 	std::string type = luaL_checkstring(L, -1);
494 	lua_pop(L, 1);
495 
496 	if(type == "node"){
497 		MAP_LOCK_REQUIRED;
498 		lua_getfield(L, 1, "pos");
499 		v3s16 pos = check_v3s16(L, -1);
500 		loc.setNodeMeta(pos);
501 
502 		if (getServerInventoryMgr(L)->getInventory(loc) != NULL)
503 			InvRef::create(L, loc);
504 		else
505 			lua_pushnil(L);
506 		return 1;
507 	}
508 
509 	NO_MAP_LOCK_REQUIRED;
510 	if (type == "player") {
511 		lua_getfield(L, 1, "name");
512 		loc.setPlayer(luaL_checkstring(L, -1));
513 		lua_pop(L, 1);
514 	} else if (type == "detached") {
515 		lua_getfield(L, 1, "name");
516 		loc.setDetached(luaL_checkstring(L, -1));
517 		lua_pop(L, 1);
518 	}
519 
520 	if (getServerInventoryMgr(L)->getInventory(loc) != NULL)
521 		InvRef::create(L, loc);
522 	else
523 		lua_pushnil(L);
524 	return 1;
525 	// END NO_MAP_LOCK_REQUIRED;
526 
527 }
528 
529 // create_detached_inventory_raw(name, [player_name])
l_create_detached_inventory_raw(lua_State * L)530 int ModApiInventory::l_create_detached_inventory_raw(lua_State *L)
531 {
532 	NO_MAP_LOCK_REQUIRED;
533 	const char *name = luaL_checkstring(L, 1);
534 	std::string player = readParam<std::string>(L, 2, "");
535 	if (getServerInventoryMgr(L)->createDetachedInventory(name, getServer(L)->idef(), player) != NULL) {
536 		InventoryLocation loc;
537 		loc.setDetached(name);
538 		InvRef::create(L, loc);
539 	} else {
540 		lua_pushnil(L);
541 	}
542 	return 1;
543 }
544 
545 // remove_detached_inventory_raw(name)
l_remove_detached_inventory_raw(lua_State * L)546 int ModApiInventory::l_remove_detached_inventory_raw(lua_State *L)
547 {
548 	NO_MAP_LOCK_REQUIRED;
549 	const std::string &name = luaL_checkstring(L, 1);
550 	lua_pushboolean(L, getServerInventoryMgr(L)->removeDetachedInventory(name));
551 	return 1;
552 }
553 
Initialize(lua_State * L,int top)554 void ModApiInventory::Initialize(lua_State *L, int top)
555 {
556 	API_FCT(create_detached_inventory_raw);
557 	API_FCT(remove_detached_inventory_raw);
558 	API_FCT(get_inventory);
559 }
560