diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index ae414406dbd568f6a3ac333e832b8c9c2dc778c9..af48c64f68b5efccc8daad45fc2a43e1acb28fcb 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -714,8 +714,8 @@ a non-equal distribution of ore.
 
 ### `sheet`
 Creates a sheet of ore in a blob shape according to the 2D perlin noise
-described by `noise_params`. This is essentially an improved version of
-the so-called "stratus" ore seen in some unofficial mods.
+described by `noise_params` and `noise_threshold`. This is essentially an
+improved version of the so-called "stratus" ore seen in some unofficial mods.
 
 This sheet consists of vertical columns of uniform randomly distributed height,
 varying between the inclusive range `column_height_min` and `column_height_max`.
@@ -731,12 +731,23 @@ the default is 0.5.
 
 The ore parameters `clust_scarcity` and `clust_num_ores` are ignored for this ore type.
 
+### `puff`
+Creates a sheet of ore in a cloud-like puff shape.
+
+As with the `sheet` ore type, the size and shape of puffs are described by
+`noise_params` and `noise_threshold` and are placed at random vertical positions
+within the currently generated chunk.
+
+The vertical top and bottom displacement of each puff are determined by the noise
+parameters `np_puff_top` and `np_puff_bottom`, respectively.
+
+
 ### `blob`
 Creates a deformed sphere of ore according to 3d perlin noise described by
 `noise_params`.  The maximum size of the blob is `clust_size`, and
 `clust_scarcity` has the same meaning as with the `scatter` type.
 
-### `vein
+### `vein`
 Creates veins of ore varying in density by according to the intersection of two
 instances of 3d perlin noise with diffferent seeds, both described by
 `noise_params`.  `random_factor` varies the influence random chance has on
@@ -771,6 +782,17 @@ Also produce this same ore between the height range of `-y_max` and `-y_min`.
 
 Useful for having ore in sky realms without having to duplicate ore entries.
 
+### `puff_cliffs`
+If set, puff ore generation will not taper down large differences in displacement
+when approaching the edge of a puff.  This flag has no effect for ore types other
+than `puff`.
+
+### `puff_additive_composition`
+By default, when noise described by `np_puff_top` or `np_puff_bottom` results in a
+negative displacement, the sub-column at that point is not generated.  With this
+attribute set, puff ore generation will instead generate the absolute difference in
+noise displacement values.  This flag has no effect for ore types other than `puff`.
+
 Decoration types
 ----------------
 The varying types of decorations that can be placed.
diff --git a/src/mg_ore.cpp b/src/mg_ore.cpp
index f5d312ba273353474ea41c0acf754ccc3298e7ca..28be816f50c18c53ab8a5535c5f51ff49a79515d 100644
--- a/src/mg_ore.cpp
+++ b/src/mg_ore.cpp
@@ -25,8 +25,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 
 FlagDesc flagdesc_ore[] = {
-	{"absheight", OREFLAG_ABSHEIGHT},
-	{NULL,        0}
+	{"absheight",                 OREFLAG_ABSHEIGHT},
+	{"puff_cliffs",               OREFLAG_PUFF_CLIFFS},
+	{"puff_additive_composition", OREFLAG_PUFF_ADDITIVE},
+	{NULL,                        0}
 };
 
 
@@ -220,6 +222,94 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed,
 
 ///////////////////////////////////////////////////////////////////////////////
 
+OrePuff::OrePuff() :
+	Ore()
+{
+	noise_puff_top    = NULL;
+	noise_puff_bottom = NULL;
+}
+
+
+OrePuff::~OrePuff()
+{
+	delete noise_puff_top;
+	delete noise_puff_bottom;
+}
+
+
+void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed,
+	v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+	PseudoRandom pr(blockseed + 4234);
+	MapNode n_ore(c_ore, 0, ore_param2);
+
+	int y_start = pr.range(nmin.Y, nmax.Y);
+
+	if (!noise) {
+		int sx = nmax.X - nmin.X + 1;
+		int sz = nmax.Z - nmin.Z + 1;
+		noise = new Noise(&np, 0, sx, sz);
+		noise_puff_top = new Noise(&np_puff_top, 0, sx, sz);
+		noise_puff_bottom = new Noise(&np_puff_bottom, 0, sx, sz);
+	}
+
+	noise->seed = mapseed + y_start;
+	noise->perlinMap2D(nmin.X, nmin.Z);
+	bool noise_generated = false;
+
+	size_t index = 0;
+	for (int z = nmin.Z; z <= nmax.Z; z++)
+	for (int x = nmin.X; x <= nmax.X; x++, index++) {
+		float noiseval = noise->result[index];
+		if (noiseval < nthresh)
+			continue;
+
+		if (biomemap && !biomes.empty()) {
+			std::set<u8>::iterator it = biomes.find(biomemap[index]);
+			if (it == biomes.end())
+				continue;
+		}
+
+		if (!noise_generated) {
+			noise_generated = true;
+			noise_puff_top->perlinMap2D(nmin.X, nmin.Z);
+			noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z);
+		}
+
+		float ntop    = noise_puff_top->result[index];
+		float nbottom = noise_puff_bottom->result[index];
+
+		if (!(flags & OREFLAG_PUFF_CLIFFS)) {
+			float ndiff = noiseval - nthresh;
+			if (ndiff < 1.0f) {
+				ntop *= ndiff;
+				nbottom *= ndiff;
+			}
+		}
+
+		int ymid = y_start;
+		int y0 = ymid - nbottom;
+		int y1 = ymid + ntop;
+
+		if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1))
+			SWAP(int, y0, y1);
+
+		for (int y = y0; y <= y1; y++) {
+			u32 i = vm->m_area.index(x, y, z);
+			if (!vm->m_area.contains(i))
+				continue;
+			if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+				continue;
+
+			vm->m_data[i] = n_ore;
+		}
+	}
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
 void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed,
 	v3s16 nmin, v3s16 nmax, u8 *biomemap)
 {
@@ -285,7 +375,8 @@ void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed,
 
 ///////////////////////////////////////////////////////////////////////////////
 
-OreVein::OreVein()
+OreVein::OreVein() :
+	Ore()
 {
 	noise2 = NULL;
 }
diff --git a/src/mg_ore.h b/src/mg_ore.h
index db204437ebf5cc0c3d0bec0022e2cf20f6908516..8ffb8fca0da4766e43b449b0695eac2fa58a653d 100644
--- a/src/mg_ore.h
+++ b/src/mg_ore.h
@@ -30,17 +30,18 @@ class MMVManip;
 
 /////////////////// Ore generation flags
 
-// Use absolute value of height to determine ore placement
-#define OREFLAG_ABSHEIGHT 0x01
-#define OREFLAG_USE_NOISE 0x08
+#define OREFLAG_ABSHEIGHT     0x01
+#define OREFLAG_PUFF_CLIFFS   0x02
+#define OREFLAG_PUFF_ADDITIVE 0x04
+#define OREFLAG_USE_NOISE     0x08
 
 #define ORE_RANGE_ACTUAL 1
 #define ORE_RANGE_MIRROR 2
 
-
 enum OreType {
 	ORE_SCATTER,
 	ORE_SHEET,
+	ORE_PUFF,
 	ORE_BLOB,
 	ORE_VEIN,
 };
@@ -95,6 +96,22 @@ class OreSheet : public Ore {
 		v3s16 nmin, v3s16 nmax, u8 *biomemap);
 };
 
+class OrePuff : public Ore {
+public:
+	static const bool NEEDS_NOISE = true;
+
+	NoiseParams np_puff_top;
+	NoiseParams np_puff_bottom;
+	Noise *noise_puff_top;
+	Noise *noise_puff_bottom;
+
+	OrePuff();
+	virtual ~OrePuff();
+
+	virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+		v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
 class OreBlob : public Ore {
 public:
 	static const bool NEEDS_NOISE = true;
@@ -134,6 +151,8 @@ class OreManager : public ObjDefManager {
 			return new OreScatter;
 		case ORE_SHEET:
 			return new OreSheet;
+		case ORE_PUFF:
+			return new OrePuff;
 		case ORE_BLOB:
 			return new OreBlob;
 		case ORE_VEIN:
diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp
index 9050816bbcfa76277fe2585ff50b050e6f151f2a..dcb611f4758d482cbeb9664ba9f6a3b314c7f2e7 100644
--- a/src/script/lua_api/l_mapgen.cpp
+++ b/src/script/lua_api/l_mapgen.cpp
@@ -70,6 +70,7 @@ struct EnumString ModApiMapgen::es_OreType[] =
 {
 	{ORE_SCATTER, "scatter"},
 	{ORE_SHEET,   "sheet"},
+	{ORE_PUFF,    "puff"},
 	{ORE_BLOB,    "blob"},
 	{ORE_VEIN,    "vein"},
 	{0, NULL},
@@ -880,7 +881,7 @@ int ModApiMapgen::l_register_ore(lua_State *L)
 				"ore_type", es_OreType, ORE_SCATTER);
 	Ore *ore = oremgr->create(oretype);
 	if (!ore) {
-		errorstream << "register_ore: ore_type " << oretype << " not implemented";
+		errorstream << "register_ore: ore_type " << oretype << " not implemented\n";
 		return 0;
 	}
 
@@ -938,20 +939,42 @@ int ModApiMapgen::l_register_ore(lua_State *L)
 	lua_pop(L, 1);
 
 	//// Get type-specific parameters
-	if (oretype == ORE_SHEET) {
-		OreSheet *oresheet = (OreSheet *)ore;
-
-		oresheet->column_height_min = getintfield_default(L, index,
-			"column_height_min", 1);
-		oresheet->column_height_max = getintfield_default(L, index,
-			"column_height_max", ore->clust_size);
-		oresheet->column_midpoint_factor = getfloatfield_default(L, index,
-			"column_midpoint_factor", 0.5f);
-	} else if (oretype == ORE_VEIN) {
-		OreVein *orevein = (OreVein *)ore;
-
-		orevein->random_factor = getfloatfield_default(L, index,
-			"random_factor", 1.f);
+	switch (oretype) {
+		case ORE_SHEET: {
+			OreSheet *oresheet = (OreSheet *)ore;
+
+			oresheet->column_height_min = getintfield_default(L, index,
+				"column_height_min", 1);
+			oresheet->column_height_max = getintfield_default(L, index,
+				"column_height_max", ore->clust_size);
+			oresheet->column_midpoint_factor = getfloatfield_default(L, index,
+				"column_midpoint_factor", 0.5f);
+
+			break;
+		}
+		case ORE_PUFF: {
+			OrePuff *orepuff = (OrePuff *)ore;
+
+			lua_getfield(L, index, "np_puff_top");
+			read_noiseparams(L, -1, &orepuff->np_puff_top);
+			lua_pop(L, 1);
+
+			lua_getfield(L, index, "np_puff_bottom");
+			read_noiseparams(L, -1, &orepuff->np_puff_bottom);
+			lua_pop(L, 1);
+
+			break;
+		}
+		case ORE_VEIN: {
+			OreVein *orevein = (OreVein *)ore;
+
+			orevein->random_factor = getfloatfield_default(L, index,
+				"random_factor", 1.f);
+
+			break;
+		}
+		default:
+			break;
 	}
 
 	ObjDefHandle handle = oremgr->add(ore);