From 103d4793f00b2dd592739f686e90370c2d8953a3 Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Tue, 29 Nov 2011 21:30:22 +0200
Subject: [PATCH] Create the necessary API for /giveme and /give and implement
 those commands; also sort out the scripts a bit

---
 data/builtin.lua                | 149 +++++++++
 data/mods/default/init.lua      |  61 ++++
 data/mods/experimental/init.lua | 146 +--------
 src/auth.cpp                    |  24 ++
 src/auth.h                      |   6 +-
 src/scriptapi.cpp               | 530 ++++++++++++++++++--------------
 6 files changed, 534 insertions(+), 382 deletions(-)

diff --git a/data/builtin.lua b/data/builtin.lua
index 5f5d40f67..dc41caf69 100644
--- a/data/builtin.lua
+++ b/data/builtin.lua
@@ -147,6 +147,155 @@ minetest.register_node("ignore", {
 	air_equivalent = true,
 })
 
+--
+-- stackstring manipulation functions
+-- example stackstring: 'CraftItem "apple" 4'
+-- example item: {type="CraftItem", name="apple"}
+-- example item: {type="ToolItem", name="SteelPick", wear="23272"}
+--
+
+function stackstring_take_item(stackstring)
+	if stackstring == nil then
+		return '', nil
+	end
+	local stacktype = nil
+	stacktype = string.match(stackstring,
+			'([%a%d]+Item[%a%d]*)')
+	if stacktype == "NodeItem" or stacktype == "CraftItem" then
+		local itemtype = nil
+		local itemname = nil
+		local itemcount = nil
+		itemtype, itemname, itemcount = string.match(stackstring,
+				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
+		itemcount = tonumber(itemcount)
+		if itemcount == 0 then
+			return '', nil
+		elseif itemcount == 1 then
+			return '', {type=itemtype, name=itemname}
+		else
+			return itemtype.." \""..itemname.."\" "..(itemcount-1),
+					{type=itemtype, name=itemname}
+		end
+	elseif stacktype == "ToolItem" then
+		local itemtype = nil
+		local itemname = nil
+		local itemwear = nil
+		itemtype, itemname, itemwear = string.match(stackstring,
+				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
+		itemwear = tonumber(itemwear)
+		return '', {type=itemtype, name=itemname, wear=itemwear}
+	end
+end
+
+function stackstring_put_item(stackstring, item)
+	if item == nil then
+		return stackstring, false
+	end
+	stackstring = stackstring or ''
+	local stacktype = nil
+	stacktype = string.match(stackstring,
+			'([%a%d]+Item[%a%d]*)')
+	stacktype = stacktype or ''
+	if stacktype ~= '' and stacktype ~= item.type then
+		return stackstring, false
+	end
+	if item.type == "NodeItem" or item.type == "CraftItem" then
+		local itemtype = nil
+		local itemname = nil
+		local itemcount = nil
+		itemtype, itemname, itemcount = string.match(stackstring,
+				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
+		itemtype = itemtype or item.type
+		itemname = itemname or item.name
+		if itemcount == nil then
+			itemcount = 0
+		end
+		itemcount = itemcount + 1
+		return itemtype.." \""..itemname.."\" "..itemcount, true
+	elseif item.type == "ToolItem" then
+		if stacktype ~= nil then
+			return stackstring, false
+		end
+		local itemtype = nil
+		local itemname = nil
+		local itemwear = nil
+		itemtype, itemname, itemwear = string.match(stackstring,
+				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
+		itemwear = tonumber(itemwear)
+		return itemtype.." \""..itemname.."\" "..itemwear, true
+	end
+	return stackstring, false
+end
+
+function stackstring_put_stackstring(stackstring, src)
+	while src ~= '' do
+		--print("src="..dump(src))
+		src, item = stackstring_take_item(src)
+		--print("src="..dump(src).." item="..dump(item))
+		local success
+		stackstring, success = stackstring_put_item(stackstring, item)
+		if not success then
+			return stackstring, false
+		end
+	end
+	return stackstring, true
+end
+
+function test_stackstring()
+	local stack
+	local item
+	local success
+
+	stack, item = stackstring_take_item('NodeItem "TNT" 3')
+	assert(stack == 'NodeItem "TNT" 2')
+	assert(item.type == 'NodeItem')
+	assert(item.name == 'TNT')
+
+	stack, item = stackstring_take_item('CraftItem "with spaces" 2')
+	assert(stack == 'CraftItem "with spaces" 1')
+	assert(item.type == 'CraftItem')
+	assert(item.name == 'with spaces')
+
+	stack, item = stackstring_take_item('CraftItem "with spaces" 1')
+	assert(stack == '')
+	assert(item.type == 'CraftItem')
+	assert(item.name == 'with spaces')
+
+	stack, item = stackstring_take_item('CraftItem "s8df2kj3" 0')
+	assert(stack == '')
+	assert(item == nil)
+
+	stack, item = stackstring_take_item('ToolItem "With Spaces" 32487')
+	assert(stack == '')
+	assert(item.type == 'ToolItem')
+	assert(item.name == 'With Spaces')
+	assert(item.wear == 32487)
+
+	stack, success = stackstring_put_item('NodeItem "With Spaces" 40',
+			{type='NodeItem', name='With Spaces'})
+	assert(stack == 'NodeItem "With Spaces" 41')
+	assert(success == true)
+
+	stack, success = stackstring_put_item('CraftItem "With Spaces" 40',
+			{type='CraftItem', name='With Spaces'})
+	assert(stack == 'CraftItem "With Spaces" 41')
+	assert(success == true)
+
+	stack, success = stackstring_put_item('ToolItem "With Spaces" 32487',
+			{type='ToolItem', name='With Spaces'})
+	assert(stack == 'ToolItem "With Spaces" 32487')
+	assert(success == false)
+
+	stack, success = stackstring_put_item('NodeItem "With Spaces" 40',
+			{type='ToolItem', name='With Spaces'})
+	assert(stack == 'NodeItem "With Spaces" 40')
+	assert(success == false)
+	
+	assert(stackstring_put_stackstring('NodeItem "With Spaces" 2',
+			'NodeItem "With Spaces" 1') == 'NodeItem "With Spaces" 3')
+end
+test_stackstring()
+
 --
 -- Callback registration
 --
diff --git a/data/mods/default/init.lua b/data/mods/default/init.lua
index 395e4a64c..f9c4c8231 100644
--- a/data/mods/default/init.lua
+++ b/data/mods/default/init.lua
@@ -26,6 +26,7 @@
 -- minetest.setting_getbool(name)
 -- minetest.chat_send_all(text)
 -- minetest.chat_send_player(name, text)
+-- minetest.get_player_privs(name)
 --
 -- Global objects:
 -- minetest.env - environment reference
@@ -52,6 +53,7 @@
 -- - add_rat(pos)
 -- - add_firefly(pos)
 -- - get_meta(pos) -- Get a NodeMetaRef at that position
+-- - get_player_by_name(name) -- Get an ObjectRef to a player
 --
 -- NodeMetaRef
 -- - get_type()
@@ -1322,6 +1324,65 @@ function on_punchnode(p, node)
 end
 minetest.register_on_punchnode(on_punchnode)
 
+minetest.register_on_chat_message(function(name, message)
+	print("default on_chat_message: name="..dump(name).." message="..dump(message))
+	local cmd = "/giveme"
+	if message:sub(0, #cmd) == cmd then
+		if not minetest.get_player_privs(name)["give"] then
+			minetest.chat_send_player(name, "you don't have permission to give")
+			return true -- Handled chat message
+		end
+		stackstring = string.match(message, cmd.." (.*)")
+		if stackstring == nil then
+			minetest.chat_send_player(name, 'usage: '..cmd..' stackstring')
+			return true -- Handled chat message
+		end
+		print(cmd..' invoked, stackstring="'..stackstring..'"')
+		player = minetest.env:get_player_by_name(name)
+		added, error_msg = player:add_to_inventory(stackstring)
+		if added then
+			minetest.chat_send_player(name, '"'..stackstring
+					..'" added to inventory.');
+		else
+			minetest.chat_send_player(name, 'Could not give "'..stackstring
+					..'": '..error_msg);
+		end
+		return true -- Handled chat message
+	end
+	local cmd = "/give"
+	if message:sub(0, #cmd) == cmd then
+		print("minetest.get_player_privs(name)="
+				..dump(minetest.get_player_privs(name)))
+		if not minetest.get_player_privs(name)["give"] then
+			minetest.chat_send_player(name, "you don't have permission to give")
+			return true -- Handled chat message
+		end
+		name2, stackstring = string.match(message, cmd.." ([%a%d_-]+) (.*)")
+		if name == nil or stackstring == nil then
+			minetest.chat_send_player(name, 'usage: '..cmd..' name stackstring')
+			return true -- Handled chat message
+		end
+		print(cmd..' invoked, name2="'..name2
+				..'" stackstring="'..stackstring..'"')
+		player = minetest.env:get_player_by_name(name2)
+		if player == nil then
+			minetest.chat_send_player(name, name2..' is not a known player')
+			return true -- Handled chat message
+		end
+		added, error_msg = player:add_to_inventory(stackstring)
+		if added then
+			minetest.chat_send_player(name, '"'..stackstring
+					..'" added to '..name2..'\'s inventory.');
+			minetest.chat_send_player(name2, '"'..stackstring
+					..'" added to inventory.');
+		else
+			minetest.chat_send_player(name, 'Could not give "'..stackstring
+					..'": '..error_msg);
+		end
+		return true -- Handled chat message
+	end
+end)
+
 --
 -- Done, print some random stuff
 --
diff --git a/data/mods/experimental/init.lua b/data/mods/experimental/init.lua
index 45f16738f..fdfc8a780 100644
--- a/data/mods/experimental/init.lua
+++ b/data/mods/experimental/init.lua
@@ -41,148 +41,6 @@ minetest.register_on_placenode(function(pos, newnode, placer)
 	end
 end)
 
-function stackstring_take_item(stackstring)
-	if stackstring == nil then
-		return '', nil
-	end
-	local stacktype = nil
-	stacktype = string.match(stackstring,
-			'([%a%d]+Item[%a%d]*)')
-	if stacktype == "NodeItem" or stacktype == "CraftItem" then
-		local itemtype = nil
-		local itemname = nil
-		local itemcount = nil
-		itemtype, itemname, itemcount = string.match(stackstring,
-				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
-		itemcount = tonumber(itemcount)
-		if itemcount == 0 then
-			return '', nil
-		elseif itemcount == 1 then
-			return '', {type=itemtype, name=itemname}
-		else
-			return itemtype.." \""..itemname.."\" "..(itemcount-1),
-					{type=itemtype, name=itemname}
-		end
-	elseif stacktype == "ToolItem" then
-		local itemtype = nil
-		local itemname = nil
-		local itemwear = nil
-		itemtype, itemname, itemwear = string.match(stackstring,
-				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
-		itemwear = tonumber(itemwear)
-		return '', {type=itemtype, name=itemname, wear=itemwear}
-	end
-end
-
-function stackstring_put_item(stackstring, item)
-	if item == nil then
-		return stackstring, false
-	end
-	stackstring = stackstring or ''
-	local stacktype = nil
-	stacktype = string.match(stackstring,
-			'([%a%d]+Item[%a%d]*)')
-	stacktype = stacktype or ''
-	if stacktype ~= '' and stacktype ~= item.type then
-		return stackstring, false
-	end
-	if item.type == "NodeItem" or item.type == "CraftItem" then
-		local itemtype = nil
-		local itemname = nil
-		local itemcount = nil
-		itemtype, itemname, itemcount = string.match(stackstring,
-				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
-		itemtype = itemtype or item.type
-		itemname = itemname or item.name
-		if itemcount == nil then
-			itemcount = 0
-		end
-		itemcount = itemcount + 1
-		return itemtype.." \""..itemname.."\" "..itemcount, true
-	elseif item.type == "ToolItem" then
-		if stacktype ~= nil then
-			return stackstring, false
-		end
-		local itemtype = nil
-		local itemname = nil
-		local itemwear = nil
-		itemtype, itemname, itemwear = string.match(stackstring,
-				'([%a%d]+Item[%a%d]*) "([^"]*)" (%d+)')
-		itemwear = tonumber(itemwear)
-		return itemtype.." \""..itemname.."\" "..itemwear, true
-	end
-	return stackstring, false
-end
-
-function stackstring_put_stackstring(stackstring, src)
-	while src ~= '' do
-		--print("src="..dump(src))
-		src, item = stackstring_take_item(src)
-		--print("src="..dump(src).." item="..dump(item))
-		local success
-		stackstring, success = stackstring_put_item(stackstring, item)
-		if not success then
-			return stackstring, false
-		end
-	end
-	return stackstring, true
-end
-
-function test_stack()
-	local stack
-	local item
-	local success
-
-	stack, item = stackstring_take_item('NodeItem "TNT" 3')
-	assert(stack == 'NodeItem "TNT" 2')
-	assert(item.type == 'NodeItem')
-	assert(item.name == 'TNT')
-
-	stack, item = stackstring_take_item('CraftItem "with spaces" 2')
-	assert(stack == 'CraftItem "with spaces" 1')
-	assert(item.type == 'CraftItem')
-	assert(item.name == 'with spaces')
-
-	stack, item = stackstring_take_item('CraftItem "with spaces" 1')
-	assert(stack == '')
-	assert(item.type == 'CraftItem')
-	assert(item.name == 'with spaces')
-
-	stack, item = stackstring_take_item('CraftItem "s8df2kj3" 0')
-	assert(stack == '')
-	assert(item == nil)
-
-	stack, item = stackstring_take_item('ToolItem "With Spaces" 32487')
-	assert(stack == '')
-	assert(item.type == 'ToolItem')
-	assert(item.name == 'With Spaces')
-	assert(item.wear == 32487)
-
-	stack, success = stackstring_put_item('NodeItem "With Spaces" 40',
-			{type='NodeItem', name='With Spaces'})
-	assert(stack == 'NodeItem "With Spaces" 41')
-	assert(success == true)
-
-	stack, success = stackstring_put_item('CraftItem "With Spaces" 40',
-			{type='CraftItem', name='With Spaces'})
-	assert(stack == 'CraftItem "With Spaces" 41')
-	assert(success == true)
-
-	stack, success = stackstring_put_item('ToolItem "With Spaces" 32487',
-			{type='ToolItem', name='With Spaces'})
-	assert(stack == 'ToolItem "With Spaces" 32487')
-	assert(success == false)
-
-	stack, success = stackstring_put_item('NodeItem "With Spaces" 40',
-			{type='ToolItem', name='With Spaces'})
-	assert(stack == 'NodeItem "With Spaces" 40')
-	assert(success == false)
-	
-	assert(stackstring_put_stackstring('NodeItem "With Spaces" 2',
-			'NodeItem "With Spaces" 1') == 'NodeItem "With Spaces" 3')
-end
-test_stack()
-
 minetest.register_abm({
 	nodenames = {"luafurnace"},
 	interval = 1.0,
@@ -426,7 +284,7 @@ print("setting max_users = " .. dump(minetest.setting_get("max_users")))
 print("setting asdf = " .. dump(minetest.setting_get("asdf")))
 
 minetest.register_on_chat_message(function(name, message)
-	print("on_chat_message: name="..dump(name).." message="..dump(message))
+	--[[print("on_chat_message: name="..dump(name).." message="..dump(message))
 	local cmd = "/testcommand"
 	if message:sub(0, #cmd) == cmd then
 		print(cmd.." invoked")
@@ -437,7 +295,7 @@ minetest.register_on_chat_message(function(name, message)
 		print("script-overridden help command")
 		minetest.chat_send_all("script-overridden help command")
 		return true
-	end
+	end]]
 end)
 
 -- Grow papyrus on TNT every 10 seconds
diff --git a/src/auth.cpp b/src/auth.cpp
index dc740411b..684391654 100644
--- a/src/auth.cpp
+++ b/src/auth.cpp
@@ -25,6 +25,26 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "strfnd.h"
 #include "debug.h"
 
+std::set<std::string> privsToSet(u64 privs)
+{
+	std::set<std::string> s;
+	if(privs & PRIV_BUILD)
+		s.insert("build");
+	if(privs & PRIV_TELEPORT)
+		s.insert("teleport");
+	if(privs & PRIV_SETTIME)
+		s.insert("settime");
+	if(privs & PRIV_PRIVS)
+		s.insert("privs");
+	if(privs & PRIV_SHOUT)
+		s.insert("shout");
+	if(privs & PRIV_BAN)
+		s.insert("ban");
+	if(privs & PRIV_GIVE)
+		s.insert("give");
+	return s;
+}
+
 // Convert a privileges value into a human-readable string,
 // with each component separated by a comma.
 std::string privsToString(u64 privs)
@@ -42,6 +62,8 @@ std::string privsToString(u64 privs)
 		os<<"shout,";
 	if(privs & PRIV_BAN)
 		os<<"ban,";
+	if(privs & PRIV_GIVE)
+		os<<"give,";
 	if(os.tellp())
 	{
 		// Drop the trailing comma. (Why on earth can't
@@ -74,6 +96,8 @@ u64 stringToPrivs(std::string str)
 			privs |= PRIV_SHOUT;
 		else if(s == "ban")
 			privs |= PRIV_BAN;
+		else if(s == "give")
+			privs |= PRIV_GIVE;
 		else
 			return PRIV_INVALID;
 	}
diff --git a/src/auth.h b/src/auth.h
index 5ea697a6a..9939632a9 100644
--- a/src/auth.h
+++ b/src/auth.h
@@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #ifndef AUTH_HEADER
 #define AUTH_HEADER
 
+#include <set>
 #include <string>
 #include <jthread.h>
 #include <jmutex.h>
-#include "common_irrlicht.h"
+#include "irrlichttypes.h"
 #include "exceptions.h"
 
 // Player privileges. These form a bitmask stored in the privs field
@@ -39,6 +40,7 @@ const u64 PRIV_SERVER = 16;          // Can manage the server (e.g. shutodwn
 const u64 PRIV_SHOUT = 32;           // Can broadcast chat messages to all
                                      // players
 const u64 PRIV_BAN = 64;             // Can ban players
+const u64 PRIV_GIVE = 128;             // Can give stuff
 
 // Default privileges - these can be overriden for new players using the
 // config option "default_privs" - however, this value still applies for
@@ -47,6 +49,8 @@ const u64 PRIV_DEFAULT = PRIV_BUILD|PRIV_SHOUT;
 const u64 PRIV_ALL = 0x7FFFFFFFFFFFFFFFULL;
 const u64 PRIV_INVALID = 0x8000000000000000ULL;
 
+std::set<std::string> privsToSet(u64 privs);
+
 // Convert a privileges value into a human-readable string,
 // with each component separated by a comma.
 std::string privsToString(u64 privs);
diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp
index 83efef670..06cf38d1e 100644
--- a/src/scriptapi.cpp
+++ b/src/scriptapi.cpp
@@ -1023,6 +1023,30 @@ static int l_chat_send_player(lua_State *L)
 	return 0;
 }
 
+// get_player_privs(name, text)
+static int l_get_player_privs(lua_State *L)
+{
+	const char *name = luaL_checkstring(L, 1);
+	// Get server from registry
+	lua_getfield(L, LUA_REGISTRYINDEX, "minetest_server");
+	Server *server = (Server*)lua_touserdata(L, -1);
+	// Do it
+	lua_newtable(L);
+	int table = lua_gettop(L);
+	u64 privs_i = server->getPlayerAuthPrivs(name);
+	// Special case for the "name" setting (local player / server owner)
+	if(name == g_settings->get("name"))
+		privs_i = PRIV_ALL;
+	std::set<std::string> privs_s = privsToSet(privs_i);
+	for(std::set<std::string>::const_iterator
+			i = privs_s.begin(); i != privs_s.end(); i++){
+		lua_pushboolean(L, true);
+		lua_setfield(L, table, i->c_str());
+	}
+	lua_pushvalue(L, table);
+	return 1;
+}
+
 static const struct luaL_Reg minetest_f [] = {
 	{"register_nodedef_defaults", l_register_nodedef_defaults},
 	{"register_entity", l_register_entity},
@@ -1035,6 +1059,7 @@ static const struct luaL_Reg minetest_f [] = {
 	{"setting_getbool", l_setting_getbool},
 	{"chat_send_all", l_chat_send_all},
 	{"chat_send_player", l_chat_send_player},
+	{"get_player_privs", l_get_player_privs},
 	{NULL, NULL}
 };
 
@@ -1502,236 +1527,6 @@ const luaL_reg NodeMetaRef::methods[] = {
 	{0,0}
 };
 
-/*
-	EnvRef
-*/
-
-class EnvRef
-{
-private:
-	ServerEnvironment *m_env;
-
-	static const char className[];
-	static const luaL_reg methods[];
-
-	static EnvRef *checkobject(lua_State *L, int narg)
-	{
-		luaL_checktype(L, narg, LUA_TUSERDATA);
-		void *ud = luaL_checkudata(L, narg, className);
-		if(!ud) luaL_typerror(L, narg, className);
-		return *(EnvRef**)ud;  // unbox pointer
-	}
-	
-	// Exported functions
-
-	// EnvRef:add_node(pos, node)
-	// pos = {x=num, y=num, z=num}
-	static int l_add_node(lua_State *L)
-	{
-		//infostream<<"EnvRef::l_add_node()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3s16 pos = readpos(L, 2);
-		// content
-		MapNode n = readnode(L, 3, env->getGameDef()->ndef());
-		// Do it
-		bool succeeded = env->getMap().addNodeWithEvent(pos, n);
-		lua_pushboolean(L, succeeded);
-		return 1;
-	}
-
-	// EnvRef:remove_node(pos)
-	// pos = {x=num, y=num, z=num}
-	static int l_remove_node(lua_State *L)
-	{
-		//infostream<<"EnvRef::l_remove_node()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3s16 pos = readpos(L, 2);
-		// Do it
-		bool succeeded = env->getMap().removeNodeWithEvent(pos);
-		lua_pushboolean(L, succeeded);
-		return 1;
-	}
-
-	// EnvRef:get_node(pos)
-	// pos = {x=num, y=num, z=num}
-	static int l_get_node(lua_State *L)
-	{
-		//infostream<<"EnvRef::l_get_node()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3s16 pos = readpos(L, 2);
-		// Do it
-		MapNode n = env->getMap().getNodeNoEx(pos);
-		// Return node
-		pushnode(L, n, env->getGameDef()->ndef());
-		return 1;
-	}
-
-	// EnvRef:add_luaentity(pos, entityname)
-	// pos = {x=num, y=num, z=num}
-	static int l_add_luaentity(lua_State *L)
-	{
-		//infostream<<"EnvRef::l_add_luaentity()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3f pos = readFloatPos(L, 2);
-		// content
-		const char *name = lua_tostring(L, 3);
-		// Do it
-		ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, "");
-		env->addActiveObject(obj);
-		return 0;
-	}
-
-	// EnvRef:add_item(pos, inventorystring)
-	// pos = {x=num, y=num, z=num}
-	static int l_add_item(lua_State *L)
-	{
-		infostream<<"EnvRef::l_add_item()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3f pos = readFloatPos(L, 2);
-		// inventorystring
-		const char *inventorystring = lua_tostring(L, 3);
-		// Do it
-		ServerActiveObject *obj = new ItemSAO(env, pos, inventorystring);
-		env->addActiveObject(obj);
-		return 0;
-	}
-
-	// EnvRef:add_rat(pos)
-	// pos = {x=num, y=num, z=num}
-	static int l_add_rat(lua_State *L)
-	{
-		infostream<<"EnvRef::l_add_rat()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3f pos = readFloatPos(L, 2);
-		// Do it
-		ServerActiveObject *obj = new RatSAO(env, pos);
-		env->addActiveObject(obj);
-		return 0;
-	}
-
-	// EnvRef:add_firefly(pos)
-	// pos = {x=num, y=num, z=num}
-	static int l_add_firefly(lua_State *L)
-	{
-		infostream<<"EnvRef::l_add_firefly()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// pos
-		v3f pos = readFloatPos(L, 2);
-		// Do it
-		ServerActiveObject *obj = new FireflySAO(env, pos);
-		env->addActiveObject(obj);
-		return 0;
-	}
-
-	// EnvRef:get_meta(pos)
-	static int l_get_meta(lua_State *L)
-	{
-		//infostream<<"EnvRef::l_get_meta()"<<std::endl;
-		EnvRef *o = checkobject(L, 1);
-		ServerEnvironment *env = o->m_env;
-		if(env == NULL) return 0;
-		// Do it
-		v3s16 p = readpos(L, 2);
-		NodeMetaRef::create(L, p, env);
-		return 1;
-	}
-
-	static int gc_object(lua_State *L) {
-		EnvRef *o = *(EnvRef **)(lua_touserdata(L, 1));
-		delete o;
-		return 0;
-	}
-
-public:
-	EnvRef(ServerEnvironment *env):
-		m_env(env)
-	{
-		infostream<<"EnvRef created"<<std::endl;
-	}
-
-	~EnvRef()
-	{
-		infostream<<"EnvRef destructing"<<std::endl;
-	}
-
-	// Creates an EnvRef and leaves it on top of stack
-	// Not callable from Lua; all references are created on the C side.
-	static void create(lua_State *L, ServerEnvironment *env)
-	{
-		EnvRef *o = new EnvRef(env);
-		//infostream<<"EnvRef::create: o="<<o<<std::endl;
-		*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
-		luaL_getmetatable(L, className);
-		lua_setmetatable(L, -2);
-	}
-
-	static void set_null(lua_State *L)
-	{
-		EnvRef *o = checkobject(L, -1);
-		o->m_env = NULL;
-	}
-	
-	static void Register(lua_State *L)
-	{
-		lua_newtable(L);
-		int methodtable = lua_gettop(L);
-		luaL_newmetatable(L, className);
-		int metatable = lua_gettop(L);
-
-		lua_pushliteral(L, "__metatable");
-		lua_pushvalue(L, methodtable);
-		lua_settable(L, metatable);  // hide metatable from Lua getmetatable()
-
-		lua_pushliteral(L, "__index");
-		lua_pushvalue(L, methodtable);
-		lua_settable(L, metatable);
-
-		lua_pushliteral(L, "__gc");
-		lua_pushcfunction(L, gc_object);
-		lua_settable(L, metatable);
-
-		lua_pop(L, 1);  // drop metatable
-
-		luaL_openlib(L, 0, methods, 0);  // fill methodtable
-		lua_pop(L, 1);  // drop methodtable
-
-		// Cannot be created from Lua
-		//lua_register(L, className, create_object);
-	}
-};
-const char EnvRef::className[] = "EnvRef";
-const luaL_reg EnvRef::methods[] = {
-	method(EnvRef, add_node),
-	method(EnvRef, remove_node),
-	method(EnvRef, get_node),
-	method(EnvRef, add_luaentity),
-	method(EnvRef, add_item),
-	method(EnvRef, add_rat),
-	method(EnvRef, add_firefly),
-	method(EnvRef, get_meta),
-	{0,0}
-};
-
 /*
 	ObjectRef
 */
@@ -1886,7 +1681,7 @@ class ObjectRef
 	}
 	
 	// add_to_inventory(self, itemstring)
-	// returns: true if item was added, false otherwise
+	// returns: true if item was added, (false, "reason") otherwise
 	static int l_add_to_inventory(lua_State *L)
 	{
 		ObjectRef *ref = checkobject(L, 1);
@@ -1902,12 +1697,23 @@ class ObjectRef
 		ServerEnvironment *env = co->getEnv();
 		assert(env);
 		IGameDef *gamedef = env->getGameDef();
-		InventoryItem *item = InventoryItem::deSerialize(is, gamedef);
-		infostream<<"item="<<env<<std::endl;
-		bool fits = co->addToInventory(item);
-		// Return
-		lua_pushboolean(L, fits);
-		return 1;
+		try{
+			InventoryItem *item = InventoryItem::deSerialize(is, gamedef);
+			if(item->getCount() == 0)
+				item->setCount(1);
+			bool added = co->addToInventory(item);
+			// Return
+			lua_pushboolean(L, added);
+			if(!added)
+				lua_pushstring(L, "does not fit");
+			return 2;
+		} catch(SerializationError &e){
+			// Return
+			lua_pushboolean(L, false);
+			lua_pushstring(L, (std::string("Invalid item: ")
+					+ e.what()).c_str());
+			return 2;
+		}
 	}
 
 	// add_to_inventory_later(self, itemstring)
@@ -2093,6 +1899,256 @@ static void objectref_get_or_create(lua_State *L,
 	}
 }
 
+/*
+	EnvRef
+*/
+
+class EnvRef
+{
+private:
+	ServerEnvironment *m_env;
+
+	static const char className[];
+	static const luaL_reg methods[];
+
+	static EnvRef *checkobject(lua_State *L, int narg)
+	{
+		luaL_checktype(L, narg, LUA_TUSERDATA);
+		void *ud = luaL_checkudata(L, narg, className);
+		if(!ud) luaL_typerror(L, narg, className);
+		return *(EnvRef**)ud;  // unbox pointer
+	}
+	
+	// Exported functions
+
+	// EnvRef:add_node(pos, node)
+	// pos = {x=num, y=num, z=num}
+	static int l_add_node(lua_State *L)
+	{
+		//infostream<<"EnvRef::l_add_node()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// pos
+		v3s16 pos = readpos(L, 2);
+		// content
+		MapNode n = readnode(L, 3, env->getGameDef()->ndef());
+		// Do it
+		bool succeeded = env->getMap().addNodeWithEvent(pos, n);
+		lua_pushboolean(L, succeeded);
+		return 1;
+	}
+
+	// EnvRef:remove_node(pos)
+	// pos = {x=num, y=num, z=num}
+	static int l_remove_node(lua_State *L)
+	{
+		//infostream<<"EnvRef::l_remove_node()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// pos
+		v3s16 pos = readpos(L, 2);
+		// Do it
+		bool succeeded = env->getMap().removeNodeWithEvent(pos);
+		lua_pushboolean(L, succeeded);
+		return 1;
+	}
+
+	// EnvRef:get_node(pos)
+	// pos = {x=num, y=num, z=num}
+	static int l_get_node(lua_State *L)
+	{
+		//infostream<<"EnvRef::l_get_node()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// pos
+		v3s16 pos = readpos(L, 2);
+		// Do it
+		MapNode n = env->getMap().getNodeNoEx(pos);
+		// Return node
+		pushnode(L, n, env->getGameDef()->ndef());
+		return 1;
+	}
+
+	// EnvRef:add_luaentity(pos, entityname)
+	// pos = {x=num, y=num, z=num}
+	static int l_add_luaentity(lua_State *L)
+	{
+		//infostream<<"EnvRef::l_add_luaentity()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// pos
+		v3f pos = readFloatPos(L, 2);
+		// content
+		const char *name = lua_tostring(L, 3);
+		// Do it
+		ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, "");
+		env->addActiveObject(obj);
+		return 0;
+	}
+
+	// EnvRef:add_item(pos, inventorystring)
+	// pos = {x=num, y=num, z=num}
+	static int l_add_item(lua_State *L)
+	{
+		infostream<<"EnvRef::l_add_item()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// pos
+		v3f pos = readFloatPos(L, 2);
+		// inventorystring
+		const char *inventorystring = lua_tostring(L, 3);
+		// Do it
+		ServerActiveObject *obj = new ItemSAO(env, pos, inventorystring);
+		env->addActiveObject(obj);
+		return 0;
+	}
+
+	// EnvRef:add_rat(pos)
+	// pos = {x=num, y=num, z=num}
+	static int l_add_rat(lua_State *L)
+	{
+		infostream<<"EnvRef::l_add_rat()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// pos
+		v3f pos = readFloatPos(L, 2);
+		// Do it
+		ServerActiveObject *obj = new RatSAO(env, pos);
+		env->addActiveObject(obj);
+		return 0;
+	}
+
+	// EnvRef:add_firefly(pos)
+	// pos = {x=num, y=num, z=num}
+	static int l_add_firefly(lua_State *L)
+	{
+		infostream<<"EnvRef::l_add_firefly()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// pos
+		v3f pos = readFloatPos(L, 2);
+		// Do it
+		ServerActiveObject *obj = new FireflySAO(env, pos);
+		env->addActiveObject(obj);
+		return 0;
+	}
+
+	// EnvRef:get_meta(pos)
+	static int l_get_meta(lua_State *L)
+	{
+		//infostream<<"EnvRef::l_get_meta()"<<std::endl;
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// Do it
+		v3s16 p = readpos(L, 2);
+		NodeMetaRef::create(L, p, env);
+		return 1;
+	}
+
+	// EnvRef:get_player_by_name(name)
+	static int l_get_player_by_name(lua_State *L)
+	{
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if(env == NULL) return 0;
+		// Do it
+		const char *name = lua_tostring(L, 2);
+		ServerRemotePlayer *player =
+				static_cast<ServerRemotePlayer*>(env->getPlayer(name));
+		if(player == NULL){
+			lua_pushnil(L);
+			return 1;
+		}
+		// Put player on stack
+		objectref_get_or_create(L, player);
+		return 1;
+	}
+
+	static int gc_object(lua_State *L) {
+		EnvRef *o = *(EnvRef **)(lua_touserdata(L, 1));
+		delete o;
+		return 0;
+	}
+
+public:
+	EnvRef(ServerEnvironment *env):
+		m_env(env)
+	{
+		infostream<<"EnvRef created"<<std::endl;
+	}
+
+	~EnvRef()
+	{
+		infostream<<"EnvRef destructing"<<std::endl;
+	}
+
+	// Creates an EnvRef and leaves it on top of stack
+	// Not callable from Lua; all references are created on the C side.
+	static void create(lua_State *L, ServerEnvironment *env)
+	{
+		EnvRef *o = new EnvRef(env);
+		//infostream<<"EnvRef::create: o="<<o<<std::endl;
+		*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
+		luaL_getmetatable(L, className);
+		lua_setmetatable(L, -2);
+	}
+
+	static void set_null(lua_State *L)
+	{
+		EnvRef *o = checkobject(L, -1);
+		o->m_env = NULL;
+	}
+	
+	static void Register(lua_State *L)
+	{
+		lua_newtable(L);
+		int methodtable = lua_gettop(L);
+		luaL_newmetatable(L, className);
+		int metatable = lua_gettop(L);
+
+		lua_pushliteral(L, "__metatable");
+		lua_pushvalue(L, methodtable);
+		lua_settable(L, metatable);  // hide metatable from Lua getmetatable()
+
+		lua_pushliteral(L, "__index");
+		lua_pushvalue(L, methodtable);
+		lua_settable(L, metatable);
+
+		lua_pushliteral(L, "__gc");
+		lua_pushcfunction(L, gc_object);
+		lua_settable(L, metatable);
+
+		lua_pop(L, 1);  // drop metatable
+
+		luaL_openlib(L, 0, methods, 0);  // fill methodtable
+		lua_pop(L, 1);  // drop methodtable
+
+		// Cannot be created from Lua
+		//lua_register(L, className, create_object);
+	}
+};
+const char EnvRef::className[] = "EnvRef";
+const luaL_reg EnvRef::methods[] = {
+	method(EnvRef, add_node),
+	method(EnvRef, remove_node),
+	method(EnvRef, get_node),
+	method(EnvRef, add_luaentity),
+	method(EnvRef, add_item),
+	method(EnvRef, add_rat),
+	method(EnvRef, add_firefly),
+	method(EnvRef, get_meta),
+	method(EnvRef, get_player_by_name),
+	{0,0}
+};
+
 /*
 	Main export function
 */
-- 
GitLab