diff --git a/data/mods/default/init.lua b/data/mods/default/init.lua
index 3954c717f60aa9a74b0a19bb2fac3d7fcc5b565f..58ac72242f28e999086a243f53897463b8df5319 100644
--- a/data/mods/default/init.lua
+++ b/data/mods/default/init.lua
@@ -47,7 +47,24 @@
 -- - get_meta(pos) -- Get a NodeMetaRef at that position
 --
 -- NodeMetaRef
--- - settext(text) -- eg. set the text of a sign
+-- - get_type()
+-- - allows_text_input()
+-- - set_text(text) -- eg. set the text of a sign
+-- - get_text()
+-- - get_owner()
+-- - set_infotext(infotext)
+-- - inventory_set_list(name, {item1, item2, ...})
+-- - inventory_get_list(name)
+-- - set_inventory_draw_spec(string)
+-- - set_allow_text_input(bool)
+-- - set_allow_removal(bool)
+-- - set_enforce_owner(bool)
+-- - is_inventory_modified()
+-- - reset_inventory_modified()
+-- - is_text_modified()
+-- - reset_text_modified()
+-- - set_string(name, value)
+-- - get_string(name)
 --
 -- ObjectRef is basically ServerActiveObject.
 -- ObjectRef methods:
@@ -1383,7 +1400,7 @@ end)
 	action = function(pos, node, active_object_count, active_object_count_wider)
 		print("ABM: Sign text changed")
 		local meta = minetest.env:get_meta(pos)
-		meta:settext("foo")
+		meta:set_text("foo")
 	end,
 })]]
 
@@ -1391,6 +1408,253 @@ end)
 --meta.setvar("somevariable", {x=0, y=0, z=0})
 --meta.getvar("somevariable") -> {x=0, y=0, z=0}
 
+--
+-- Random stuff
+--
+
+minetest.register_node("luafurnace", {
+	tile_images = {"lava.png", "furnace_side.png", "furnace_side.png",
+		"furnace_side.png", "furnace_side.png", "furnace_front.png"},
+	--inventory_image = "furnace_front.png",
+	inventory_image = inventorycube("furnace_front.png"),
+	paramtype = "facedir_simple",
+	metadata_name = "generic",
+	material = digprop_stonelike(3.0),
+})
+
+minetest.register_on_placenode(function(pos, newnode, placer)
+	if newnode.name == "luafurnace" then
+		print("get_meta");
+		local meta = minetest.env:get_meta(pos)
+		print("inventory_set_list");
+		meta:inventory_set_list("fuel", {""})
+		print("inventory_set_list");
+		meta:inventory_set_list("src", {""})
+		print("inventory_set_list");
+		meta:inventory_set_list("dst", {"","","",""})
+		print("set_inventory_draw_spec");
+		meta:set_inventory_draw_spec(
+			"invsize[8,9;]"
+			.."list[current_name;fuel;2,3;1,1;]"
+			.."list[current_name;src;2,1;1,1;]"
+			.."list[current_name;dst;5,1;2,2;]"
+			.."list[current_player;main;0,5;8,4;]"
+		)
+		
+		local total_cooked = 0;
+		print("set_string")
+		meta:set_string("total_cooked", total_cooked)
+		print("set_infotext");
+		meta:set_infotext("Lua Furnace: total cooked: "..total_cooked)
+	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,
+	chance = 1,
+	action = function(pos, node, active_object_count, active_object_count_wider)
+		local meta = minetest.env:get_meta(pos)
+		local fuellist = meta:inventory_get_list("fuel")
+		local srclist = meta:inventory_get_list("src")
+		local dstlist = meta:inventory_get_list("dst")
+		if fuellist == nil or srclist == nil or dstlist == nil then
+			return
+		end
+		_, srcitem = stackstring_take_item(srclist[1])
+		_, fuelitem = stackstring_take_item(fuellist[1])
+		if not srcitem or not fuelitem then return end
+		if fuelitem.type == "NodeItem" then
+			local prop = minetest.registered_nodes[fuelitem.name]
+			if prop == nil then return end
+			if prop.furnace_burntime < 0 then return end
+		else
+			return
+		end
+		local resultstack = nil
+		if srcitem.type == "NodeItem" then
+			local prop = minetest.registered_nodes[srcitem.name]
+			if prop == nil then return end
+			if prop.cookresult_item == "" then return end
+			resultstack = prop.cookresult_item
+		else
+			return
+		end
+
+		if resultstack == nil then
+			return
+		end
+
+		dstlist[1], success = stackstring_put_stackstring(dstlist[1], resultstack)
+		if not success then
+			return
+		end
+
+		fuellist[1], _ = stackstring_take_item(fuellist[1])
+		srclist[1], _ = stackstring_take_item(srclist[1])
+
+		meta:inventory_set_list("fuel", fuellist)
+		meta:inventory_set_list("src", srclist)
+		meta:inventory_set_list("dst", dstlist)
+
+		local total_cooked = meta:get_string("total_cooked")
+		total_cooked = tonumber(total_cooked) + 1
+		meta:set_string("total_cooked", total_cooked)
+		meta:set_infotext("Lua Furnace: total cooked: "..total_cooked)
+	end,
+})
+
+minetest.register_craft({
+	output = 'NodeItem "luafurnace" 1',
+	recipe = {
+		{'NodeItem "cobble"', 'NodeItem "cobble"', 'NodeItem "cobble"'},
+		{'NodeItem "cobble"', 'NodeItem "cobble"', 'NodeItem "cobble"'},
+		{'NodeItem "cobble"', 'NodeItem "cobble"', 'NodeItem "cobble"'},
+	}
+})
+
 --
 -- Done, print some random stuff
 --
diff --git a/src/content_nodemeta.cpp b/src/content_nodemeta.cpp
index 72be1df3917be419bd382948502fe90fc7a6eb89..0641941866cc4e7790a83cd2a77e38b6f8804325 100644
--- a/src/content_nodemeta.cpp
+++ b/src/content_nodemeta.cpp
@@ -18,14 +18,119 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "content_nodemeta.h"
+
+#include <map>
 #include "inventory.h"
 #include "log.h"
 #include "utility.h"
 
+class Inventory;
+
+#define NODEMETA_GENERIC 1
 #define NODEMETA_SIGN 14
 #define NODEMETA_CHEST 15
-#define NODEMETA_LOCKABLE_CHEST 17
 #define NODEMETA_FURNACE 16
+#define NODEMETA_LOCKABLE_CHEST 17
+
+class SignNodeMetadata : public NodeMetadata
+{
+public:
+	SignNodeMetadata(IGameDef *gamedef, std::string text);
+	//~SignNodeMetadata();
+	
+	virtual u16 typeId() const;
+	virtual const char* typeName() const
+	{ return "sign"; }
+	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
+	static NodeMetadata* create(IGameDef *gamedef);
+	virtual NodeMetadata* clone(IGameDef *gamedef);
+	virtual void serializeBody(std::ostream &os);
+	virtual std::string infoText();
+
+	virtual bool allowsTextInput(){ return true; }
+	virtual std::string getText(){ return m_text; }
+	virtual void setText(const std::string &t){ m_text = t; }
+
+private:
+	std::string m_text;
+};
+
+class ChestNodeMetadata : public NodeMetadata
+{
+public:
+	ChestNodeMetadata(IGameDef *gamedef);
+	~ChestNodeMetadata();
+	
+	virtual u16 typeId() const;
+	virtual const char* typeName() const
+	{ return "chest"; }
+	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
+	static NodeMetadata* create(IGameDef *gamedef);
+	virtual NodeMetadata* clone(IGameDef *gamedef);
+	virtual void serializeBody(std::ostream &os);
+	virtual std::string infoText();
+	virtual Inventory* getInventory() {return m_inventory;}
+	virtual bool nodeRemovalDisabled();
+	virtual std::string getInventoryDrawSpecString();
+	
+private:
+	Inventory *m_inventory;
+};
+
+class LockingChestNodeMetadata : public NodeMetadata
+{
+public:
+	LockingChestNodeMetadata(IGameDef *gamedef);
+	~LockingChestNodeMetadata();
+
+	virtual u16 typeId() const;
+	virtual const char* typeName() const
+	{ return "locked_chest"; }
+	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
+	static NodeMetadata* create(IGameDef *gamedef);
+	virtual NodeMetadata* clone(IGameDef *gamedef);
+	virtual void serializeBody(std::ostream &os);
+	virtual std::string infoText();
+	virtual Inventory* getInventory() {return m_inventory;}
+	virtual bool nodeRemovalDisabled();
+	virtual std::string getInventoryDrawSpecString();
+
+	virtual std::string getOwner(){ return m_text; }
+	virtual void setOwner(std::string t){ m_text = t; }
+
+private:
+	Inventory *m_inventory;
+	std::string m_text;
+};
+
+class FurnaceNodeMetadata : public NodeMetadata
+{
+public:
+	FurnaceNodeMetadata(IGameDef *gamedef);
+	~FurnaceNodeMetadata();
+	
+	virtual u16 typeId() const;
+	virtual const char* typeName() const
+	{ return "furnace"; }
+	virtual NodeMetadata* clone(IGameDef *gamedef);
+	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
+	static NodeMetadata* create(IGameDef *gamedef);
+	virtual void serializeBody(std::ostream &os);
+	virtual std::string infoText();
+	virtual Inventory* getInventory() {return m_inventory;}
+	virtual void inventoryModified();
+	virtual bool step(float dtime);
+	virtual bool nodeRemovalDisabled();
+	virtual std::string getInventoryDrawSpecString();
+
+private:
+	Inventory *m_inventory;
+	float m_step_accumulator;
+	float m_fuel_totaltime;
+	float m_fuel_time;
+	float m_src_totaltime;
+	float m_src_time;
+};
 
 /*
 	SignNodeMetadata
@@ -428,4 +533,229 @@ std::string FurnaceNodeMetadata::getInventoryDrawSpecString()
 		"list[current_player;main;0,5;8,4;]";
 }
 
+/*
+	GenericNodeMetadata
+*/
+
+class GenericNodeMetadata : public NodeMetadata
+{
+private:
+	Inventory *m_inventory;
+	std::string m_text;
+	std::string m_owner;
+
+	std::string m_infotext;
+	std::string m_inventorydrawspec;
+	bool m_allow_text_input;
+	bool m_removal_disabled;
+	bool m_enforce_owner;
+
+	bool m_inventory_modified;
+	bool m_text_modified;
+
+	std::map<std::string, std::string> m_stringvars;
+
+public:
+	u16 typeId() const
+	{
+		return NODEMETA_GENERIC;
+	}
+	const char* typeName() const
+	{
+		return "generic";
+	}
+
+	GenericNodeMetadata(IGameDef *gamedef):
+		NodeMetadata(gamedef),
+
+		m_inventory(new Inventory()),
+		m_text(""),
+		m_owner(""),
+
+		m_infotext("GenericNodeMetadata"),
+		m_inventorydrawspec(""),
+		m_allow_text_input(false),
+		m_removal_disabled(false),
+		m_enforce_owner(false),
+
+		m_inventory_modified(false),
+		m_text_modified(false)
+	{
+		NodeMetadata::registerType(typeId(), typeName(), create, create);
+	}
+	virtual ~GenericNodeMetadata()
+	{
+		delete m_inventory;
+	}
+	NodeMetadata* clone(IGameDef *gamedef)
+	{
+		GenericNodeMetadata *d = new GenericNodeMetadata(m_gamedef);
+
+		*d->m_inventory = *m_inventory;
+		d->m_text = m_text;
+		d->m_owner = m_owner;
+
+		d->m_infotext = m_infotext;
+		d->m_inventorydrawspec = m_inventorydrawspec;
+		d->m_allow_text_input = m_allow_text_input;
+		d->m_removal_disabled = m_removal_disabled;
+		d->m_enforce_owner = m_enforce_owner;
+		d->m_inventory_modified = m_inventory_modified;
+		d->m_text_modified = m_text_modified;
+		return d;
+	}
+	static NodeMetadata* create(IGameDef *gamedef)
+	{
+		GenericNodeMetadata *d = new GenericNodeMetadata(gamedef);
+		return d;
+	}
+	static NodeMetadata* create(std::istream &is, IGameDef *gamedef)
+	{
+		GenericNodeMetadata *d = new GenericNodeMetadata(gamedef);
+		
+		d->m_inventory->deSerialize(is, gamedef);
+		d->m_text = deSerializeLongString(is);
+		d->m_owner = deSerializeString(is);
+		
+		d->m_infotext = deSerializeString(is);
+		d->m_inventorydrawspec = deSerializeString(is);
+		d->m_allow_text_input = readU8(is);
+		d->m_removal_disabled = readU8(is);
+		d->m_enforce_owner = readU8(is);
+
+		int num_vars = readU32(is);
+		for(int i=0; i<num_vars; i++){
+			std::string name = deSerializeString(is);
+			std::string var = deSerializeLongString(is);
+			d->m_stringvars[name] = var;
+		}
+
+		return d;
+	}
+	void serializeBody(std::ostream &os)
+	{
+		m_inventory->serialize(os);
+		os<<serializeLongString(m_text);
+		os<<serializeString(m_owner);
+
+		os<<serializeString(m_infotext);
+		os<<serializeString(m_inventorydrawspec);
+		writeU8(os, m_allow_text_input);
+		writeU8(os, m_removal_disabled);
+		writeU8(os, m_enforce_owner);
+
+		int num_vars = m_stringvars.size();
+		writeU32(os, num_vars);
+		for(std::map<std::string, std::string>::iterator
+				i = m_stringvars.begin(); i != m_stringvars.end(); i++){
+			os<<serializeString(i->first);
+			os<<serializeLongString(i->second);
+		}
+	}
+
+	std::string infoText()
+	{
+		return m_infotext;
+	}
+	Inventory* getInventory()
+	{
+		return m_inventory;
+	}
+	void inventoryModified()
+	{
+		m_inventory_modified = true;
+	}
+	bool step(float dtime)
+	{
+		return false;
+	}
+	bool nodeRemovalDisabled()
+	{
+		return m_removal_disabled;
+	}
+	std::string getInventoryDrawSpecString()
+	{
+		return m_inventorydrawspec;
+	}
+	bool allowsTextInput()
+	{
+		return m_allow_text_input;
+	}
+	std::string getText()
+	{
+		return m_text;
+	}
+	void setText(const std::string &t)
+	{
+		m_text = t;
+		m_text_modified = true;
+	}
+	std::string getOwner()
+	{
+		if(m_enforce_owner)
+			return m_owner;
+		else
+			return "";
+	}
+	void setOwner(std::string t)
+	{
+		m_owner = t;
+	}
+	
+	/* Interface for GenericNodeMetadata */
+
+	void setInfoText(const std::string &text)
+	{
+		infostream<<"GenericNodeMetadata::setInfoText(\""
+				<<text<<"\")"<<std::endl;
+		m_infotext = text;
+	}
+	void setInventoryDrawSpec(const std::string &text)
+	{
+		m_inventorydrawspec = text;
+	}
+	void setAllowTextInput(bool b)
+	{
+		m_allow_text_input = b;
+	}
+	void setRemovalDisabled(bool b)
+	{
+		m_removal_disabled = b;
+	}
+	void setEnforceOwner(bool b)
+	{
+		m_enforce_owner = b;
+	}
+	bool isInventoryModified()
+	{
+		return m_inventory_modified;
+	}
+	void resetInventoryModified()
+	{
+		m_inventory_modified = false;
+	}
+	bool isTextModified()
+	{
+		return m_text_modified;
+	}
+	void resetTextModified()
+	{
+		m_text_modified = false;
+	}
+	void setString(const std::string &name, const std::string &var)
+	{
+		m_stringvars[name] = var;
+	}
+	std::string getString(const std::string &name)
+	{
+		std::map<std::string, std::string>::iterator i;
+		i = m_stringvars.find(name);
+		if(i == m_stringvars.end())
+			return "";
+		return i->second;
+	}
+};
+
+// Prototype
+GenericNodeMetadata proto_GenericNodeMetadata(NULL);
 
diff --git a/src/content_nodemeta.h b/src/content_nodemeta.h
index 8888d6f1fb50105ea00d81e0304cfa1ed973ca6d..6ce7a22be59d6d02ab34c3e11b7ba5337c624196 100644
--- a/src/content_nodemeta.h
+++ b/src/content_nodemeta.h
@@ -22,108 +22,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "nodemetadata.h"
 
-class Inventory;
-
-class SignNodeMetadata : public NodeMetadata
-{
-public:
-	SignNodeMetadata(IGameDef *gamedef, std::string text);
-	//~SignNodeMetadata();
-	
-	virtual u16 typeId() const;
-	virtual const char* typeName() const
-	{ return "sign"; }
-	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
-	static NodeMetadata* create(IGameDef *gamedef);
-	virtual NodeMetadata* clone(IGameDef *gamedef);
-	virtual void serializeBody(std::ostream &os);
-	virtual std::string infoText();
-
-	virtual bool allowsTextInput(){ return true; }
-	virtual std::string getText(){ return m_text; }
-	virtual void setText(const std::string &t){ m_text = t; }
-
-private:
-	std::string m_text;
-};
-
-class ChestNodeMetadata : public NodeMetadata
-{
-public:
-	ChestNodeMetadata(IGameDef *gamedef);
-	~ChestNodeMetadata();
-	
-	virtual u16 typeId() const;
-	virtual const char* typeName() const
-	{ return "chest"; }
-	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
-	static NodeMetadata* create(IGameDef *gamedef);
-	virtual NodeMetadata* clone(IGameDef *gamedef);
-	virtual void serializeBody(std::ostream &os);
-	virtual std::string infoText();
-	virtual Inventory* getInventory() {return m_inventory;}
-	virtual bool nodeRemovalDisabled();
-	virtual std::string getInventoryDrawSpecString();
-	
-private:
-	Inventory *m_inventory;
-};
-
-class LockingChestNodeMetadata : public NodeMetadata
-{
-public:
-	LockingChestNodeMetadata(IGameDef *gamedef);
-	~LockingChestNodeMetadata();
-
-	virtual u16 typeId() const;
-	virtual const char* typeName() const
-	{ return "locked_chest"; }
-	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
-	static NodeMetadata* create(IGameDef *gamedef);
-	virtual NodeMetadata* clone(IGameDef *gamedef);
-	virtual void serializeBody(std::ostream &os);
-	virtual std::string infoText();
-	virtual Inventory* getInventory() {return m_inventory;}
-	virtual bool nodeRemovalDisabled();
-	virtual std::string getInventoryDrawSpecString();
-
-	virtual std::string getOwner(){ return m_text; }
-	virtual void setOwner(std::string t){ m_text = t; }
-
-private:
-	Inventory *m_inventory;
-	std::string m_text;
-};
-
-class FurnaceNodeMetadata : public NodeMetadata
-{
-public:
-	FurnaceNodeMetadata(IGameDef *gamedef);
-	~FurnaceNodeMetadata();
-	
-	virtual u16 typeId() const;
-	virtual const char* typeName() const
-	{ return "furnace"; }
-	virtual NodeMetadata* clone(IGameDef *gamedef);
-	static NodeMetadata* create(std::istream &is, IGameDef *gamedef);
-	static NodeMetadata* create(IGameDef *gamedef);
-	virtual void serializeBody(std::ostream &os);
-	virtual std::string infoText();
-	virtual Inventory* getInventory() {return m_inventory;}
-	virtual void inventoryModified();
-	virtual bool step(float dtime);
-	virtual bool nodeRemovalDisabled();
-	virtual std::string getInventoryDrawSpecString();
-
-private:
-	Inventory *m_inventory;
-	float m_step_accumulator;
-	float m_fuel_totaltime;
-	float m_fuel_time;
-	float m_src_totaltime;
-	float m_src_time;
-};
-
-
 #endif
 
diff --git a/src/map.cpp b/src/map.cpp
index 0de534f4d99c2334e1ad94a397be0047557b9cd1..34bc31ba4098300c5a3b1cbde388ae1f6450ad96 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -1007,8 +1007,13 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 	std::string metadata_name = nodemgr->get(n).metadata_name;
 	if(metadata_name != ""){
 		NodeMetadata *meta = NodeMetadata::create(metadata_name, m_gamedef);
-		meta->setOwner(player_name);
-		setNodeMetadata(p, meta);
+		if(!meta){
+			errorstream<<"Failed to create node metadata \""
+					<<metadata_name<<"\""<<std::endl;
+		} else {
+			meta->setOwner(player_name);
+			setNodeMetadata(p, meta);
+		}
 	}
 
 	/*
diff --git a/src/nodemetadata.h b/src/nodemetadata.h
index 37668268e21038892eb9fc6499a6a8a0444a2838..19ce80a42ca4ec8c2c30c886e6102cadd41cb5af 100644
--- a/src/nodemetadata.h
+++ b/src/nodemetadata.h
@@ -53,23 +53,54 @@ class NodeMetadata
 	virtual const char* typeName() const = 0;
 	virtual NodeMetadata* clone(IGameDef *gamedef) = 0;
 	virtual void serializeBody(std::ostream &os) = 0;
+
+	// Called on client-side; shown on screen when pointed at
 	virtual std::string infoText() {return "";}
+	
+	//
 	virtual Inventory* getInventory() {return NULL;}
-	// This is called always after the inventory is modified, before
-	// the changes are copied elsewhere
+	// Called always after the inventory is modified, before the changes
+	// are copied elsewhere
 	virtual void inventoryModified(){}
-	// A step in time. Returns true if metadata changed.
+
+	// A step in time. Shall return true if metadata changed.
 	virtual bool step(float dtime) {return false;}
+
+	// Whether the related node and this metadata cannot be removed
 	virtual bool nodeRemovalDisabled(){return false;}
-	// Used to make custom inventory menus.
+	// If non-empty, player can interact by using an inventory view
 	// See format in guiInventoryMenu.cpp.
 	virtual std::string getInventoryDrawSpecString(){return "";}
-	// primarily used for locking chests, but others can play too
-	virtual std::string getOwner(){ return std::string(""); }
-	virtual void setOwner(std::string t){}
+
+	// If true, player can interact by writing text
 	virtual bool allowsTextInput(){ return false; }
+	// Get old text for player interaction
 	virtual std::string getText(){ return ""; }
+	// Set player-written text
 	virtual void setText(const std::string &t){}
+
+	// If returns non-empty, only given player can modify text/inventory
+	virtual std::string getOwner(){ return std::string(""); }
+	// The name of the player who placed the node
+	virtual void setOwner(std::string t){}
+
+	/* Interface for GenericNodeMetadata */
+
+	virtual void setInfoText(const std::string &text){};
+	virtual void setInventoryDrawSpec(const std::string &text){};
+	virtual void setAllowTextInput(bool b){};
+
+	virtual void setRemovalDisabled(bool b){};
+	virtual void setEnforceOwner(bool b){};
+
+	virtual bool isInventoryModified(){return false;};
+	virtual void resetInventoryModified(){};
+	virtual bool isTextModified(){return false;};
+	virtual void resetTextModified(){};
+
+	virtual void setString(const std::string &name, const std::string &var){}
+	virtual std::string getString(const std::string &name){return "";}
+
 protected:
 	static void registerType(u16 id, const std::string &name, Factory f,
 			Factory2 f2);
diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp
index bf2868907aabcf80c3b4fde870ec53a7a8ba3d4e..8191879763bb92d85ab337f630d679a23bd8fcee 100644
--- a/src/scriptapi.cpp
+++ b/src/scriptapi.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "scriptapi.h"
 
 #include <iostream>
+#include <list>
 extern "C" {
 #include <lua.h>
 #include <lualib.h>
@@ -42,26 +43,7 @@ extern "C" {
 #include "settings.h" // For accessing g_settings
 #include "nodemetadata.h"
 #include "mapblock.h" // For getNodeBlockPos
-
-/*
-TODO:
-- All kinds of callbacks
-- LuaNodeMetadata
-	blockdef.metadata_name =
-		""
-		"sign"
-		"furnace"
-		"chest"
-		"locked_chest"
-		"lua"
-	- Stores an inventory and stuff in a Settings object
-	meta.inventory_add_list("main")
-	blockdef.on_inventory_modified
-	meta.set("owner", playername)
-	meta.get("owner")
-- Item definition (actually, only CraftItem)
-- Putting items in NodeMetadata (?)
-*/
+#include "content_nodemeta.h"
 
 static void stackDump(lua_State *L, std::ostream &o)
 {
@@ -1069,6 +1051,16 @@ class NodeMetaRef
 		return meta;
 	}
 
+	/*static IGenericNodeMetadata* getgenericmeta(NodeMetaRef *ref)
+	{
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL)
+			return NULL;
+		if(meta->typeId() != NODEMETA_GENERIC)
+			return NULL;
+		return (IGenericNodeMetadata*)meta;
+	}*/
+
 	static void reportMetadataChange(NodeMetaRef *ref)
 	{
 		// Inform other things that the metadata has changed
@@ -1080,7 +1072,8 @@ class NodeMetaRef
 		// Set the block to be saved
 		MapBlock *block = ref->m_env->getMap().getBlockNoCreateNoEx(blockpos);
 		if(block)
-			block->raiseModified(MOD_STATE_WRITE_NEEDED, "l_settext");
+			block->raiseModified(MOD_STATE_WRITE_NEEDED,
+					"NodeMetaRef::reportMetadataChange");
 	}
 	
 	// Exported functions
@@ -1092,8 +1085,33 @@ class NodeMetaRef
 		return 0;
 	}
 
-	// settext(self, text)
-	static int l_settext(lua_State *L)
+	// get_type(self)
+	static int l_get_type(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL){
+			lua_pushnil(L);
+			return 1;
+		}
+		// Do it
+		lua_pushstring(L, meta->typeName());
+		return 1;
+	}
+
+	// allows_text_input(self)
+	static int l_allows_text_input(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		lua_pushboolean(L, meta->allowsTextInput());
+		return 1;
+	}
+
+	// set_text(self, text)
+	static int l_set_text(lua_State *L)
 	{
 		NodeMetaRef *ref = checkobject(L, 1);
 		NodeMetadata *meta = getmeta(ref);
@@ -1101,11 +1119,260 @@ class NodeMetaRef
 		// Do it
 		std::string text = lua_tostring(L, 2);
 		meta->setText(text);
-		// Inform other things that the metadata has changed
 		reportMetadataChange(ref);
 		return 0;
 	}
 
+	// get_text(self)
+	static int l_get_text(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		std::string text = meta->getText();
+		lua_pushstring(L, text.c_str());
+		return 1;
+	}
+
+	// get_owner(self)
+	static int l_get_owner(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		std::string owner = meta->getOwner();
+		lua_pushstring(L, owner.c_str());
+		return 1;
+	}
+
+	/* IGenericNodeMetadata interface */
+	
+	// set_infotext(self, text)
+	static int l_set_infotext(lua_State *L)
+	{
+		infostream<<__FUNCTION_NAME<<std::endl;
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		std::string text = lua_tostring(L, 2);
+		meta->setInfoText(text);
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// inventory_set_list(self, name, {item1, item2, ...})
+	static int l_inventory_set_list(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		Inventory *inv = meta->getInventory();
+		std::string name = lua_tostring(L, 2);
+		// If nil, delete list
+		if(lua_isnil(L, 3)){
+			inv->deleteList(name);
+			return 0;
+		}
+		// Otherwise set list
+		std::list<std::string> items;
+		luaL_checktype(L, 3, LUA_TTABLE);
+		int table = 3;
+		lua_pushnil(L);
+		infostream<<"items: ";
+		while(lua_next(L, table) != 0){
+			// key at index -2 and value at index -1
+			luaL_checktype(L, -1, LUA_TSTRING);
+			std::string itemstring = lua_tostring(L, -1);
+			infostream<<"\""<<itemstring<<"\" ";
+			items.push_back(itemstring);
+			// removes value, keeps key for next iteration
+			lua_pop(L, 1);
+		}
+		infostream<<std::endl;
+		InventoryList *invlist = inv->addList(name, items.size());
+		int index = 0;
+		for(std::list<std::string>::const_iterator
+				i = items.begin(); i != items.end(); i++){
+			const std::string &itemstring = *i;
+			InventoryItem *newitem = NULL;
+			if(itemstring != "")
+				newitem = InventoryItem::deSerialize(itemstring,
+						ref->m_env->getGameDef());
+			InventoryItem *olditem = invlist->changeItem(index, newitem);
+			delete olditem;
+			index++;
+		}
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// inventory_get_list(self, name)
+	static int l_inventory_get_list(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		Inventory *inv = meta->getInventory();
+		std::string name = lua_tostring(L, 2);
+		InventoryList *invlist = inv->getList(name);
+		if(invlist == NULL){
+			lua_pushnil(L);
+			return 1;
+		}
+		// Get the table insert function
+		lua_getglobal(L, "table");
+		lua_getfield(L, -1, "insert");
+		int table_insert = lua_gettop(L);
+		// Create and fill table
+		lua_newtable(L);
+		int table = lua_gettop(L);
+		for(u32 i=0; i<invlist->getSize(); i++){
+			InventoryItem *item = invlist->getItem(i);
+			lua_pushvalue(L, table_insert);
+			lua_pushvalue(L, table);
+			if(item == NULL){
+				lua_pushnil(L);
+			} else {
+				lua_pushstring(L, item->getItemString().c_str());
+			}
+			if(lua_pcall(L, 2, 0, 0))
+				script_error(L, "error: %s\n", lua_tostring(L, -1));
+		}
+		return 1;
+	}
+
+	// set_inventory_draw_spec(self, text)
+	static int l_set_inventory_draw_spec(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		std::string text = lua_tostring(L, 2);
+		meta->setInventoryDrawSpec(text);
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// set_allow_text_input(self, text)
+	static int l_set_allow_text_input(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		bool b = lua_toboolean(L, 2);
+		meta->setAllowTextInput(b);
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// set_allow_removal(self, text)
+	static int l_set_allow_removal(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		bool b = lua_toboolean(L, 2);
+		meta->setRemovalDisabled(!b);
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// set_enforce_owner(self, text)
+	static int l_set_enforce_owner(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		bool b = lua_toboolean(L, 2);
+		meta->setEnforceOwner(b);
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// is_inventory_modified(self)
+	static int l_is_inventory_modified(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		lua_pushboolean(L, meta->isInventoryModified());
+		return 1;
+	}
+
+	// reset_inventory_modified(self)
+	static int l_reset_inventory_modified(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		meta->resetInventoryModified();
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// is_text_modified(self)
+	static int l_is_text_modified(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		lua_pushboolean(L, meta->isTextModified());
+		return 1;
+	}
+
+	// reset_text_modified(self)
+	static int l_reset_text_modified(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		meta->resetTextModified();
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// set_string(self, name, var)
+	static int l_set_string(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		std::string name = lua_tostring(L, 2);
+		size_t len = 0;
+		const char *s = lua_tolstring(L, 3, &len);
+		std::string str(s, len);
+		meta->setString(name, str);
+		reportMetadataChange(ref);
+		return 0;
+	}
+
+	// get_string(self, name)
+	static int l_get_string(lua_State *L)
+	{
+		NodeMetaRef *ref = checkobject(L, 1);
+		NodeMetadata *meta = getmeta(ref);
+		if(meta == NULL) return 0;
+		// Do it
+		std::string name = lua_tostring(L, 2);
+		std::string str = meta->getString(name);
+		lua_pushlstring(L, str.c_str(), str.size());
+		return 1;
+	}
+
 public:
 	NodeMetaRef(v3s16 p, ServerEnvironment *env):
 		m_p(p),
@@ -1158,7 +1425,24 @@ class NodeMetaRef
 };
 const char NodeMetaRef::className[] = "NodeMetaRef";
 const luaL_reg NodeMetaRef::methods[] = {
-	method(NodeMetaRef, settext),
+	method(NodeMetaRef, get_type),
+	method(NodeMetaRef, allows_text_input),
+	method(NodeMetaRef, set_text),
+	method(NodeMetaRef, get_text),
+	method(NodeMetaRef, get_owner),
+	method(NodeMetaRef, set_infotext),
+	method(NodeMetaRef, inventory_set_list),
+	method(NodeMetaRef, inventory_get_list),
+	method(NodeMetaRef, set_inventory_draw_spec),
+	method(NodeMetaRef, set_allow_text_input),
+	method(NodeMetaRef, set_allow_removal),
+	method(NodeMetaRef, set_enforce_owner),
+	method(NodeMetaRef, is_inventory_modified),
+	method(NodeMetaRef, reset_inventory_modified),
+	method(NodeMetaRef, is_text_modified),
+	method(NodeMetaRef, reset_text_modified),
+	method(NodeMetaRef, set_string),
+	method(NodeMetaRef, get_string),
 	{0,0}
 };
 
@@ -1188,7 +1472,7 @@ class EnvRef
 	// pos = {x=num, y=num, z=num}
 	static int l_add_node(lua_State *L)
 	{
-		infostream<<"EnvRef::l_add_node()"<<std::endl;
+		//infostream<<"EnvRef::l_add_node()"<<std::endl;
 		EnvRef *o = checkobject(L, 1);
 		ServerEnvironment *env = o->m_env;
 		if(env == NULL) return 0;
@@ -1206,7 +1490,7 @@ class EnvRef
 	// pos = {x=num, y=num, z=num}
 	static int l_remove_node(lua_State *L)
 	{
-		infostream<<"EnvRef::l_remove_node()"<<std::endl;
+		//infostream<<"EnvRef::l_remove_node()"<<std::endl;
 		EnvRef *o = checkobject(L, 1);
 		ServerEnvironment *env = o->m_env;
 		if(env == NULL) return 0;
@@ -1222,7 +1506,7 @@ class EnvRef
 	// pos = {x=num, y=num, z=num}
 	static int l_get_node(lua_State *L)
 	{
-		infostream<<"EnvRef::l_get_node()"<<std::endl;
+		//infostream<<"EnvRef::l_get_node()"<<std::endl;
 		EnvRef *o = checkobject(L, 1);
 		ServerEnvironment *env = o->m_env;
 		if(env == NULL) return 0;
@@ -1239,7 +1523,7 @@ class EnvRef
 	// pos = {x=num, y=num, z=num}
 	static int l_add_luaentity(lua_State *L)
 	{
-		infostream<<"EnvRef::l_add_luaentity()"<<std::endl;
+		//infostream<<"EnvRef::l_add_luaentity()"<<std::endl;
 		EnvRef *o = checkobject(L, 1);
 		ServerEnvironment *env = o->m_env;
 		if(env == NULL) return 0;
@@ -1256,7 +1540,7 @@ class EnvRef
 	// EnvRef:get_meta(pos)
 	static int l_get_meta(lua_State *L)
 	{
-		infostream<<"EnvRef::l_get_meta()"<<std::endl;
+		//infostream<<"EnvRef::l_get_meta()"<<std::endl;
 		EnvRef *o = checkobject(L, 1);
 		ServerEnvironment *env = o->m_env;
 		if(env == NULL) return 0;
@@ -1777,7 +2061,7 @@ void scriptapi_add_object_reference(lua_State *L, ServerActiveObject *cobj)
 {
 	realitycheck(L);
 	assert(lua_checkstack(L, 20));
-	infostream<<"scriptapi_add_object_reference: id="<<cobj->getId()<<std::endl;
+	//infostream<<"scriptapi_add_object_reference: id="<<cobj->getId()<<std::endl;
 	StackUnroller stack_unroller(L);
 
 	// Create object on stack
@@ -1800,7 +2084,7 @@ void scriptapi_rm_object_reference(lua_State *L, ServerActiveObject *cobj)
 {
 	realitycheck(L);
 	assert(lua_checkstack(L, 20));
-	infostream<<"scriptapi_rm_object_reference: id="<<cobj->getId()<<std::endl;
+	//infostream<<"scriptapi_rm_object_reference: id="<<cobj->getId()<<std::endl;
 	StackUnroller stack_unroller(L);
 
 	// Get minetest.object_refs table
diff --git a/src/server.cpp b/src/server.cpp
index 51f8814feb92f4b5e66dd59375ce98a86a7f377c..63172e955850a50e668b2761ec7fc0c8bfd4ff78 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -3309,9 +3309,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 						p.Y = stoi(fn.next(","));
 						p.Z = stoi(fn.next(","));
 						NodeMetadata *meta = m_env->getMap().getNodeMetadata(p);
-						if(meta && meta->typeId() == LEGN(m_nodedef, "CONTENT_LOCKABLE_CHEST")) {
-							LockingChestNodeMetadata *lcm = (LockingChestNodeMetadata*)meta;
-							if (lcm->getOwner() != player->getName())
+						if(meta->getOwner() != ""){
+							if(meta->getOwner() != player->getName())
 								disable_action = true;
 						}
 					}
@@ -3327,9 +3326,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 						p.Y = stoi(fn.next(","));
 						p.Z = stoi(fn.next(","));
 						NodeMetadata *meta = m_env->getMap().getNodeMetadata(p);
-						if(meta && meta->typeId() == LEGN(m_nodedef, "CONTENT_LOCKABLE_CHEST")) {
-							LockingChestNodeMetadata *lcm = (LockingChestNodeMetadata*)meta;
-							if (lcm->getOwner() != player->getName())
+						if(meta->getOwner() != ""){
+							if(meta->getOwner() != player->getName())
 								disable_action = true;
 						}
 					}
@@ -3356,9 +3354,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
 						p.Y = stoi(fn.next(","));
 						p.Z = stoi(fn.next(","));
 						NodeMetadata *meta = m_env->getMap().getNodeMetadata(p);
-						if(meta && meta->typeId() == LEGN(m_nodedef, "CONTENT_LOCKABLE_CHEST")) {
-							LockingChestNodeMetadata *lcm = (LockingChestNodeMetadata*)meta;
-							if (lcm->getOwner() != player->getName())
+						if(meta->getOwner() != ""){
+							if(meta->getOwner() != player->getName())
 								disable_action = true;
 						}
 					}