diff --git a/mesecons/internal.lua b/mesecons/internal.lua
index 6fdc3f94a46593629755d3540838f8dad7631a2d..044e3bfa782e9a5ddba0ece0a6a4389b38fa82ad 100644
--- a/mesecons/internal.lua
+++ b/mesecons/internal.lua
@@ -92,8 +92,8 @@ function mesecon.get_any_inputrules(node)
 end
 
 function mesecon.get_any_rules(node)
-	return mesecon.mergetable(mesecon.get_any_inputrules(node) or {},
-		mesecon.get_any_outputrules(node) or {})
+	return mesecon.merge_rule_sets(mesecon.get_any_inputrules(node),
+		mesecon.get_any_outputrules(node))
 end
 
 -- Receptors
diff --git a/mesecons/presets.lua b/mesecons/presets.lua
index a2062d928a7db9493234cf124849ed1b325e3f4f..e10dd3634a8d785ccaccf2e06b443c3cc920b243 100644
--- a/mesecons/presets.lua
+++ b/mesecons/presets.lua
@@ -16,9 +16,9 @@ mesecon.rules.default = {
 	{x =  0, y = -1, z = -1},
 }
 
-mesecon.rules.floor = mesecon.mergetable(mesecon.rules.default, {{x = 0, y = -1, z = 0}})
+mesecon.rules.floor = mesecon.merge_rule_sets(mesecon.rules.default, {{x = 0, y = -1, z = 0}})
 
-mesecon.rules.pplate = mesecon.mergetable(mesecon.rules.floor, {{x = 0, y = -2, z = 0}})
+mesecon.rules.pplate = mesecon.merge_rule_sets(mesecon.rules.floor, {{x = 0, y = -2, z = 0}})
 
 mesecon.rules.buttonlike = {
 	{x = 1,  y =  0, z =  0},
diff --git a/mesecons/util.lua b/mesecons/util.lua
index f1f88d6751abac8cda5db6cc5d176b1e804f3e5c..3cdeb644ccfc011ae99d9c63acd52dc0a4243364 100644
--- a/mesecons/util.lua
+++ b/mesecons/util.lua
@@ -213,8 +213,9 @@ function mesecon.cmpAny(t1, t2)
 	return true
 end
 
--- does not overwrite values; number keys (ipairs) are appended, not overwritten
+-- Deprecated. Use `merge_tables` or `merge_rule_sets` as appropriate.
 function mesecon.mergetable(source, dest)
+	minetest.log("warning", debug.traceback("Deprecated call to mesecon.mergetable"))
 	local rval = mesecon.tablecopy(dest)
 
 	for k, v in pairs(source) do
@@ -227,6 +228,32 @@ function mesecon.mergetable(source, dest)
 	return rval
 end
 
+-- Merges several rule sets in one. Order may not be preserved. Nil arguments
+-- are ignored.
+-- The rule sets must be of the same kind (either all single-level or all two-level).
+-- The function may be changed to normalize the resulting set in some way.
+function mesecon.merge_rule_sets(...)
+	local rval = {}
+	for _, t in pairs({...}) do -- ignores nils automatically
+		table.insert_all(rval, mesecon.tablecopy(t))
+	end
+	return rval
+end
+
+-- Merges two tables, with entries from `replacements` taking precedence over
+-- those from `base`. Returns the new table.
+-- Values are deep-copied from either table, keys are referenced.
+-- Numerical indices aren’t handled specially.
+function mesecon.merge_tables(base, replacements)
+	local ret = mesecon.tablecopy(replacements) -- these are never overriden so have to be copied in any case
+	for k, v in pairs(base) do
+		if ret[k] == nil then -- it could be `false`
+			ret[k] = mesecon.tablecopy(v)
+		end
+	end
+	return ret
+end
+
 function mesecon.register_node(name, spec_common, spec_off, spec_on)
 	spec_common.drop = spec_common.drop or name .. "_off"
 	spec_common.on_blast = spec_common.on_blast or mesecon.on_blastnode
@@ -234,8 +261,8 @@ function mesecon.register_node(name, spec_common, spec_off, spec_on)
 	spec_on.__mesecon_state = "on"
 	spec_off.__mesecon_state = "off"
 
-	spec_on = mesecon.mergetable(spec_common, spec_on);
-	spec_off = mesecon.mergetable(spec_common, spec_off);
+	spec_on = mesecon.merge_tables(spec_common, spec_on);
+	spec_off = mesecon.merge_tables(spec_common, spec_off);
 
 	minetest.register_node(name .. "_on", spec_on)
 	minetest.register_node(name .. "_off", spec_off)
diff --git a/mesecons_blinkyplant/init.lua b/mesecons_blinkyplant/init.lua
index 14a274f45016d2d4bf6e47d521bf768c89ff13c3..80b6b7eb20c6eb777709916ed794794a7ad03fbc 100644
--- a/mesecons_blinkyplant/init.lua
+++ b/mesecons_blinkyplant/init.lua
@@ -32,7 +32,13 @@ mesecon.register_node("mesecons_blinkyplant:blinky_plant", {
 		fixed = {-0.3, -0.5, -0.3, 0.3, -0.5+0.7, 0.3},
 	},
 	on_timer = on_timer,
-	on_rightclick = toggle_timer,
+	on_rightclick = function(pos, node, clicker)
+		if minetest.is_protected(pos, clicker and clicker:get_player_name() or "") then
+			return
+		end
+
+		toggle_timer(pos)
+	end,
 	on_construct = toggle_timer
 },{
 	tiles = {"jeija_blinky_plant_off.png"},
diff --git a/mesecons_delayer/init.lua b/mesecons_delayer/init.lua
index 66665adcac80060e8da689014ebfa34969f71574..d392633df750dbb9714b9a0fd6204f22febce76f 100644
--- a/mesecons_delayer/init.lua
+++ b/mesecons_delayer/init.lua
@@ -93,7 +93,7 @@ local off_state = {
 	wield_image = "mesecons_delayer_off_1.png",
 	groups = off_groups,
 	on_punch = function(pos, node, puncher)
-		if minetest.is_protected(pos, puncher and puncher:get_player_name()) then
+		if minetest.is_protected(pos, puncher and puncher:get_player_name() or "") then
 			return
 		end
 
@@ -134,7 +134,7 @@ local on_state = {
 	},
 	groups = {bendy = 2, snappy = 1, dig_immediate = 2, not_in_creative_inventory = 1},
 	on_punch = function(pos, node, puncher)
-		if minetest.is_protected(pos, puncher and puncher:get_player_name()) then
+		if minetest.is_protected(pos, puncher and puncher:get_player_name() or "") then
 			return
 		end
 
diff --git a/mesecons_extrawires/mesewire.lua b/mesecons_extrawires/mesewire.lua
index 455f75f011bd772be0ac5d145cb2607d9d20e92c..519129fc09b7aba25d4a892d78278c12440babe3 100644
--- a/mesecons_extrawires/mesewire.lua
+++ b/mesecons_extrawires/mesewire.lua
@@ -18,7 +18,7 @@ minetest.override_item("default:mese", {
 
 -- Copy node definition of powered mese from normal mese
 -- and brighten texture tiles to indicate mese is powered
-local powered_def = mesecon.mergetable(minetest.registered_nodes["default:mese"], {
+local powered_def = mesecon.merge_tables(minetest.registered_nodes["default:mese"], {
 	drop = "default:mese",
 	light_source = 5,
 	mesecons = {conductor = {
diff --git a/mesecons_noteblock/init.lua b/mesecons_noteblock/init.lua
index 577cee0895b9c90a618b87ac26aa8ee282a1aa41..40e2577b859f600885397bc2fa25f3e51176819d 100644
--- a/mesecons_noteblock/init.lua
+++ b/mesecons_noteblock/init.lua
@@ -4,7 +4,7 @@ minetest.register_node("mesecons_noteblock:noteblock", {
 	is_ground_content = false,
 	groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2},
 	on_punch = function(pos, node, puncher) -- change sound when punched
-		if minetest.is_protected(pos, puncher and puncher:get_player_name()) then
+		if minetest.is_protected(pos, puncher and puncher:get_player_name() or "") then
 			return
 		end