This patch was retrieved from upstream to fix a bug where the furnaces
are blinking like hell, making them near to unusable.

Upstream patches:
https://github.com/minetest/minetest/commit/06baf05c641355ead97e9428c4455af9e8b11cef
https://github.com/minetest/minetest/commit/d879a539cd19ddd1ee34afec2512fb2238de2822
https://github.com/minetest/minetest_game/commit/47a49eccf4bd59427fa7486fd413e6eade1db896

---
 doc/lua_api.txt                               |    2 ++
 games/minetest_game/mods/default/nodes.lua    |   15 +++++----------
 games/minetest_game/mods/doors/init.lua       |    8 ++------
 games/minetest_game/mods/screwdriver/init.lua |    6 +-----
 games/minimal/mods/default/init.lua           |   15 +++++----------
 src/client.cpp                                |   12 +++++++++---
 src/client.h                                  |    2 +-
 src/clientserver.h                            |    8 +++++++-
 src/environment.cpp                           |    5 +++++
 src/environment.h                             |    1 +
 src/map.cpp                                   |   14 ++++++++------
 src/map.h                                     |    9 +++++++--
 src/script/lua_api/l_env.cpp                  |   17 +++++++++++++++++
 src/script/lua_api/l_env.h                    |    4 ++++
 src/server.cpp                                |   21 ++++++++++++++++-----
 src/server.h                                  |    3 ++-
 16 files changed, 92 insertions(+), 50 deletions(-)

Index: b/doc/lua_api.txt
===================================================================
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -1254,6 +1254,8 @@
 minetest.set_node(pos, node)
 minetest.add_node(pos, node): alias set_node(pos, node)
 ^ Set node at position (node = {name="foo", param1=0, param2=0})
+minetest.swap_node(pos, node)
+^ Set node at position, but don't remove metadata
 minetest.remove_node(pos)
 ^ Equivalent to set_node(pos, "air")
 minetest.get_node(pos)
Index: b/games/minetest_game/mods/default/nodes.lua
===================================================================
--- a/games/minetest_game/mods/default/nodes.lua
+++ b/games/minetest_game/mods/default/nodes.lua
@@ -942,18 +942,13 @@
 	end,
 })
 
-function hacky_swap_node(pos,name)
+local function swap_node(pos,name)
 	local node = minetest.get_node(pos)
-	local meta = minetest.get_meta(pos)
-	local meta0 = meta:to_table()
 	if node.name == name then
 		return
 	end
 	node.name = name
-	local meta0 = meta:to_table()
-	minetest.set_node(pos,node)
-	meta = minetest.get_meta(pos)
-	meta:from_table(meta0)
+	minetest.swap_node(pos,node)
 end
 
 minetest.register_abm({
@@ -1007,7 +1002,7 @@
 			local percent = math.floor(meta:get_float("fuel_time") /
 					meta:get_float("fuel_totaltime") * 100)
 			meta:set_string("infotext","Furnace active: "..percent.."%")
-			hacky_swap_node(pos,"default:furnace_active")
+			swap_node(pos,"default:furnace_active")
 			meta:set_string("formspec",default.get_furnace_active_formspec(pos, percent))
 			return
 		end
@@ -1027,7 +1022,7 @@
 
 		if fuel.time <= 0 then
 			meta:set_string("infotext","Furnace out of fuel")
-			hacky_swap_node(pos,"default:furnace")
+			swap_node(pos,"default:furnace")
 			meta:set_string("formspec", default.furnace_inactive_formspec)
 			return
 		end
@@ -1035,7 +1030,7 @@
 		if cooked.item:is_empty() then
 			if was_active then
 				meta:set_string("infotext","Furnace is empty")
-				hacky_swap_node(pos,"default:furnace")
+				swap_node(pos,"default:furnace")
 				meta:set_string("formspec", default.furnace_inactive_formspec)
 			end
 			return
Index: b/games/minetest_game/mods/doors/init.lua
===================================================================
--- a/games/minetest_game/mods/doors/init.lua
+++ b/games/minetest_game/mods/doors/init.lua
@@ -113,14 +113,10 @@
 		local p2 = minetest.get_node(pos).param2
 		p2 = params[p2+1]
 		
-		local meta = minetest.get_meta(pos):to_table()
-		minetest.set_node(pos, {name=replace_dir, param2=p2})
-		minetest.get_meta(pos):from_table(meta)
+		minetest.swap_node(pos, {name=replace_dir, param2=p2})
 		
 		pos.y = pos.y-dir
-		meta = minetest.get_meta(pos):to_table()
-		minetest.set_node(pos, {name=replace, param2=p2})
-		minetest.get_meta(pos):from_table(meta)
+		minetest.swap_node(pos, {name=replace, param2=p2})
 	end
 	
 	local function check_player_priv(pos, player)
Index: b/games/minetest_game/mods/screwdriver/init.lua
===================================================================
--- a/games/minetest_game/mods/screwdriver/init.lua
+++ b/games/minetest_game/mods/screwdriver/init.lua
@@ -113,12 +113,8 @@
 				end
 			end
 			--print (dump(axisdir..", "..rotation))
-			local meta = minetest.get_meta(pos)
-			local meta0 = meta:to_table()
 			node.param2 = n
-			minetest.set_node(pos,node)
-			meta = minetest.get_meta(pos)
-			meta:from_table(meta0)
+			minetest.swap_node(pos,node)
 			local item=itemstack:to_table()
 			local item_wear=tonumber((item["wear"]))
 			item_wear=item_wear+327 
Index: b/games/minimal/mods/default/init.lua
===================================================================
--- a/games/minimal/mods/default/init.lua
+++ b/games/minimal/mods/default/init.lua
@@ -1334,18 +1334,13 @@
 	end,
 })
 
-function hacky_swap_node(pos,name)
+function swap_node(pos,name)
 	local node = minetest.get_node(pos)
-	local meta = minetest.get_meta(pos)
-	local meta0 = meta:to_table()
 	if node.name == name then
 		return
 	end
 	node.name = name
-	local meta0 = meta:to_table()
-	minetest.set_node(pos,node)
-	meta = minetest.get_meta(pos)
-	meta:from_table(meta0)
+	minetest.swap_node(pos, node)
 end
 
 minetest.register_abm({
@@ -1400,7 +1395,7 @@
 			local percent = math.floor(meta:get_float("fuel_time") /
 					meta:get_float("fuel_totaltime") * 100)
 			meta:set_string("infotext","Furnace active: "..percent.."%")
-			hacky_swap_node(pos,"default:furnace_active")
+			swap_node(pos,"default:furnace_active")
 			meta:set_string("formspec",
 				"size[8,9]"..
 				"image[2,2;1,1;default_furnace_fire_bg.png^[lowpart:"..
@@ -1426,7 +1421,7 @@
 
 		if fuel.time <= 0 then
 			meta:set_string("infotext","Furnace out of fuel")
-			hacky_swap_node(pos,"default:furnace")
+			swap_node(pos,"default:furnace")
 			meta:set_string("formspec", default.furnace_inactive_formspec)
 			return
 		end
@@ -1434,7 +1429,7 @@
 		if cooked.item:is_empty() then
 			if was_active then
 				meta:set_string("infotext","Furnace is empty")
-				hacky_swap_node(pos,"default:furnace")
+				swap_node(pos,"default:furnace")
 				meta:set_string("formspec", default.furnace_inactive_formspec)
 			end
 			return
Index: b/src/client.cpp
===================================================================
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -1262,7 +1262,13 @@
 		MapNode n;
 		n.deSerialize(&data[8], ser_version);
 		
-		addNode(p, n);
+		bool remove_metadata = true;
+		u32 index = 8 + MapNode::serializedLength(ser_version);
+		if ((datasize >= index+1) && data[index]){
+			remove_metadata = false;
+		}	
+		
+		addNode(p, n, remove_metadata);
 	}
 	else if(command == TOCLIENT_BLOCKDATA)
 	{
@@ -2514,7 +2520,7 @@
 	}
 }
 
-void Client::addNode(v3s16 p, MapNode n)
+void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
 {
 	TimeTaker timer1("Client::addNode()");
 
@@ -2523,7 +2529,7 @@
 	try
 	{
 		//TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
-		m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
+		m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
 	}
 	catch(InvalidPositionException &e)
 	{}
Index: b/src/client.h
===================================================================
--- a/src/client.h
+++ b/src/client.h
@@ -365,7 +365,7 @@
 	
 	// Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent)
 	void removeNode(v3s16 p);
-	void addNode(v3s16 p, MapNode n);
+	void addNode(v3s16 p, MapNode n, bool remove_metadata = true);
 	
 	void setPlayerControl(PlayerControl &control);
 
Index: b/src/clientserver.h
===================================================================
--- a/src/clientserver.h
+++ b/src/clientserver.h
@@ -102,7 +102,7 @@
 			added to object properties
 */
 
-#define LATEST_PROTOCOL_VERSION 21
+#define LATEST_PROTOCOL_VERSION 22
 
 // Server's supported network protocol range
 #define SERVER_PROTOCOL_VERSION_MIN 13
@@ -139,6 +139,12 @@
 
 	TOCLIENT_BLOCKDATA = 0x20, //TODO: Multiple blocks
 	TOCLIENT_ADDNODE = 0x21,
+	/*
+		u16 command
+		v3s16 position
+		serialized mapnode
+		u8 keep_metadata // Added in protocol version 22
+	*/
 	TOCLIENT_REMOVENODE = 0x22,
 	
 	TOCLIENT_PLAYERPOS = 0x23, // Obsolete
Index: b/src/environment.cpp
===================================================================
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -874,6 +874,11 @@
 	return true;
 }
 
+bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
+{
+	return m_map->addNodeWithEvent(p, n, false);
+}
+
 std::set<u16> ServerEnvironment::getObjectsInsideRadius(v3f pos, float radius)
 {
 	std::set<u16> objects;
Index: b/src/environment.h
===================================================================
--- a/src/environment.h
+++ b/src/environment.h
@@ -283,6 +283,7 @@
 	// Script-aware node setters
 	bool setNode(v3s16 p, const MapNode &n);
 	bool removeNode(v3s16 p);
+	bool swapNode(v3s16 p, const MapNode &n);
 	
 	// Find all active objects inside a radius around a point
 	std::set<u16> getObjectsInsideRadius(v3f pos, float radius);
Index: b/src/map.cpp
===================================================================
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -931,7 +931,8 @@
 /*
 */
 void Map::addNodeAndUpdate(v3s16 p, MapNode n,
-		std::map<v3s16, MapBlock*> &modified_blocks)
+		std::map<v3s16, MapBlock*> &modified_blocks,
+		bool remove_metadata)
 {
 	INodeDefManager *ndef = m_gamedef->ndef();
 
@@ -1018,8 +1019,9 @@
 	/*
 		Remove node metadata
 	*/
-
-	removeNodeMetadata(p);
+	if (remove_metadata) {
+		removeNodeMetadata(p);
+	}
 
 	/*
 		Set the node on the map
@@ -1319,17 +1321,17 @@
 	}
 }
 
-bool Map::addNodeWithEvent(v3s16 p, MapNode n)
+bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata)
 {
 	MapEditEvent event;
-	event.type = MEET_ADDNODE;
+	event.type = remove_metadata ? MEET_ADDNODE : MEET_SWAPNODE;
 	event.p = p;
 	event.n = n;
 
 	bool succeeded = true;
 	try{
 		std::map<v3s16, MapBlock*> modified_blocks;
-		addNodeAndUpdate(p, n, modified_blocks);
+		addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
 
 		// Copy modified_blocks to event
 		for(std::map<v3s16, MapBlock*>::iterator
Index: b/src/map.h
===================================================================
--- a/src/map.h
+++ b/src/map.h
@@ -61,6 +61,8 @@
 	MEET_ADDNODE,
 	// Node removed (changed to air)
 	MEET_REMOVENODE,
+	// Node swapped (changed without metadata change)
+	MEET_SWAPNODE,
 	// Node metadata of block changed (not knowing which node exactly)
 	// p stores block coordinate
 	MEET_BLOCK_NODE_METADATA_CHANGED,
@@ -99,6 +101,8 @@
 			return VoxelArea(p);
 		case MEET_REMOVENODE:
 			return VoxelArea(p);
+		case MEET_SWAPNODE:
+			return VoxelArea(p);
 		case MEET_BLOCK_NODE_METADATA_CHANGED:
 		{
 			v3s16 np1 = p*MAP_BLOCKSIZE;
@@ -236,7 +240,8 @@
 		These handle lighting but not faces.
 	*/
 	void addNodeAndUpdate(v3s16 p, MapNode n,
-			std::map<v3s16, MapBlock*> &modified_blocks);
+			std::map<v3s16, MapBlock*> &modified_blocks,
+			bool remove_metadata = true);
 	void removeNodeAndUpdate(v3s16 p,
 			std::map<v3s16, MapBlock*> &modified_blocks);
 
@@ -245,7 +250,7 @@
 		These emit events.
 		Return true if succeeded, false if not.
 	*/
-	bool addNodeWithEvent(v3s16 p, MapNode n);
+	bool addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata = true);
 	bool removeNodeWithEvent(v3s16 p);
 
 	/*
Index: b/src/script/lua_api/l_env.cpp
===================================================================
--- a/src/script/lua_api/l_env.cpp
+++ b/src/script/lua_api/l_env.cpp
@@ -120,6 +120,22 @@
 	return 1;
 }
 
+// minetest.swap_node(pos, node)
+// pos = {x=num, y=num, z=num}
+int ModApiEnvMod::l_swap_node(lua_State *L)
+{
+	GET_ENV_PTR;
+
+	INodeDefManager *ndef = env->getGameDef()->ndef();
+	// parameters
+	v3s16 pos = read_v3s16(L, 1);
+	MapNode n = readnode(L, 2, ndef);
+	// Do it
+	bool succeeded = env->swapNode(pos, n);
+	lua_pushboolean(L, succeeded);
+	return 1;
+}
+
 // minetest.get_node(pos)
 // pos = {x=num, y=num, z=num}
 int ModApiEnvMod::l_get_node(lua_State *L)
@@ -796,6 +812,7 @@
 {
 	API_FCT(set_node);
 	API_FCT(add_node);
+	API_FCT(swap_node);
 	API_FCT(add_item);
 	API_FCT(remove_node);
 	API_FCT(get_node);
Index: b/src/script/lua_api/l_env.h
===================================================================
--- a/src/script/lua_api/l_env.h
+++ b/src/script/lua_api/l_env.h
@@ -34,6 +34,10 @@
 	// minetest.remove_node(pos)
 	// pos = {x=num, y=num, z=num}
 	static int l_remove_node(lua_State *L);
+	
+	// minetest.swap_node(pos, node)
+	// pos = {x=num, y=num, z=num}
+	static int l_swap_node(lua_State *L);
 
 	// minetest.get_node(pos)
 	// pos = {x=num, y=num, z=num}
Index: b/src/server.cpp
===================================================================
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -1582,16 +1582,16 @@
 			// for them.
 			std::list<u16> far_players;
 
-			if(event->type == MEET_ADDNODE)
+			if(event->type == MEET_ADDNODE || event->type == MEET_SWAPNODE)
 			{
 				//infostream<<"Server: MEET_ADDNODE"<<std::endl;
 				prof.add("MEET_ADDNODE", 1);
 				if(disable_single_change_sending)
 					sendAddNode(event->p, event->n, event->already_known_by_peer,
-							&far_players, 5);
+							&far_players, 5, event->type == MEET_ADDNODE);
 				else
 					sendAddNode(event->p, event->n, event->already_known_by_peer,
-							&far_players, 30);
+							&far_players, 30, event->type == MEET_ADDNODE);
 			}
 			else if(event->type == MEET_REMOVENODE)
 			{
@@ -4070,7 +4070,8 @@
 }
 
 void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
-		std::list<u16> *far_players, float far_d_nodes)
+		std::list<u16> *far_players, float far_d_nodes,
+		bool remove_metadata)
 {
 	float maxd = far_d_nodes*BS;
 	v3f p_f = intToFloat(p, BS);
@@ -4106,13 +4107,23 @@
 		}
 
 		// Create packet
-		u32 replysize = 8 + MapNode::serializedLength(client->serialization_version);
+		u32 replysize = 9 + MapNode::serializedLength(client->serialization_version);
 		SharedBuffer<u8> reply(replysize);
 		writeU16(&reply[0], TOCLIENT_ADDNODE);
 		writeS16(&reply[2], p.X);
 		writeS16(&reply[4], p.Y);
 		writeS16(&reply[6], p.Z);
 		n.serialize(&reply[8], client->serialization_version);
+		u32 index = 8 + MapNode::serializedLength(client->serialization_version);
+		writeU8(&reply[index], remove_metadata ? 0 : 1);
+		
+		if (!remove_metadata) {
+			if (client->net_proto_version <= 21) {
+				// Old clients always clear metadata; fix it
+				// by sending the full block again.
+				client->SetBlockNotSent(p);
+			}
+		}
 
 		// Send as reliable
 		m_con.Send(client->peer_id, 0, reply, true);
Index: b/src/server.h
===================================================================
--- a/src/server.h
+++ b/src/server.h
@@ -556,7 +556,8 @@
 	void sendRemoveNode(v3s16 p, u16 ignore_id=0,
 			std::list<u16> *far_players=NULL, float far_d_nodes=100);
 	void sendAddNode(v3s16 p, MapNode n, u16 ignore_id=0,
-			std::list<u16> *far_players=NULL, float far_d_nodes=100);
+			std::list<u16> *far_players=NULL, float far_d_nodes=100,
+			bool remove_metadata=true);
 	void setBlockNotSent(v3s16 p);
 
 	// Environment and Connection must be locked when called
