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