function dye.on_place(itemstack, placer, pointed_thing)
	if (not pointed_thing) or (pointed_thing.type ~= "node") then return end
	local pos = pointed_thing.under
	if minetest.is_protected(pos, placer:get_player_name()) then return end
	local node = minetest.get_node_or_nil(pos)
	if not node then return end
	local def = minetest.registered_nodes[node.name]
	if not def then return end

	-- execute node's on_rightclick first
	local rc = voxelgarden.call_on_rightclick(itemstack, placer, pointed_thing)
	if rc then return rc end

	-- just place down the dye if the node isn't dyeable
	if not def._dyestr then
		return minetest.item_place(itemstack, placer, pointed_thing)
	end

	local color = itemstack:get_name():gsub(".*:", "") -- "dye:red" -> "red"
	local new_name

	if def._dyemap then
		local new_color = def._dyemap[color] or color
		new_name = string.format(def._dyestr, new_color)
	else
		new_name = string.format(def._dyestr, color)
	end

	-- swap the node and play the sound
	minetest.swap_node(pos, {name=new_name})
	minetest.sound_play(def.sounds.dug,
		{pos = pos, max_hear_distance = 16, gain = def.sounds.dug.gain})

	-- update the score manually since this isn't technically placing
	vg_score.update(placer)

	if not minetest.is_creative_enabled(placer:get_player_name()) then
		itemstack:take_item()
		return itemstack
	else
		return
	end
end
