diff --git a/minetest.conf.example b/minetest.conf.example
index 6559ac88bd8710052a6549deed534b50f41012b1..49fc95ba2f01395497f31fef48940d7123015408 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -239,6 +239,10 @@
 #congestion_control_aim_rtt = 0.2
 #congestion_control_max_rate = 400
 #congestion_control_min_rate = 10
+# Specifies URL from which client fetches media instead of using UDP
+# $filename should be accessible from $remote_media$filename via cURL
+# (obviously, remote_media should end with a slash)
+# Files that are not present would be fetched the usual way
 #remote_media =
 
 # Mapgen stuff
diff --git a/src/biome.cpp b/src/biome.cpp
index 180a9c4a5b414ea2bb4ea1c9b963b600ce5efacb..34d51839fd3a1cf432274a66380cde5126535003 100644
--- a/src/biome.cpp
+++ b/src/biome.cpp
@@ -127,7 +127,7 @@ void BiomeDefManager::addBiome(Biome *b) {
 	bgroup->push_back(b);
 
 	verbosestream << "BiomeDefManager: added biome '" << b->name <<
-		"' to biome group " << b->groupid << std::endl;
+		"' to biome group " << (int)b->groupid << std::endl;
 }
 
 
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index e6526d3d78d8b10cf1bc7023686b9beee741c51e..a164d0693e805ea3824ef319691e6621d3580a9c 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -177,7 +177,7 @@ void set_default_settings(Settings *settings)
 	settings->setDefault("mg_name", "v6");
 	settings->setDefault("water_level", "1");
 	settings->setDefault("chunksize", "5");
-	settings->setDefault("mg_flags", "19");
+	settings->setDefault("mg_flags", "trees, caves, v6_biome_blend");
 	settings->setDefault("mgv6_freq_desert", "0.45");
 	settings->setDefault("mgv6_freq_beach", "0.15");
 
diff --git a/src/map.cpp b/src/map.cpp
index ea82194b85ccca4211797f6de18d51d08d256674..717b0cf9bf9c2398ec83b8de3e7bff68060880b4 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -2013,10 +2013,10 @@ ServerMap::ServerMap(std::string savedir, IGameDef *gamedef, EmergeManager *emer
 
 	if (g_settings->get("fixed_map_seed").empty())
 	{
-		m_seed = (((u64)(myrand()%0xffff)<<0)
-				+ ((u64)(myrand()%0xffff)<<16)
-				+ ((u64)(myrand()%0xffff)<<32)
-				+ ((u64)(myrand()&0xffff)<<48));
+		m_seed = (((u64)(myrand() & 0xffff) << 0)
+				| ((u64)(myrand() & 0xffff) << 16)
+				| ((u64)(myrand() & 0xffff) << 32)
+				| ((u64)(myrand() & 0xffff) << 48));
 		m_mgparams->seed = m_seed;
 	}
 
@@ -3078,14 +3078,7 @@ void ServerMap::saveMapMeta()
 
 	Settings params;
 
-	params.set("mg_name", m_emerge->params->mg_name);
-	params.setU64("seed", m_emerge->params->seed);
-	params.setS16("water_level", m_emerge->params->water_level);
-	params.setS16("chunksize", m_emerge->params->chunksize);
-	params.setS32("mg_flags", m_emerge->params->flags);
-
-	m_emerge->params->writeParams(&params);
-
+	m_emerge->setParamsToSettings(&params);
 	params.writeLines(os);
 
 	os<<"[end_of_params]\n";
diff --git a/src/mapgen.cpp b/src/mapgen.cpp
index f2745bdb4347f38d7f7bd1b629c068758b768fe3..b19073e9017d8c703a8f1780ce55db9245d57bb5 100644
--- a/src/mapgen.cpp
+++ b/src/mapgen.cpp
@@ -35,6 +35,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "treegen.h"
 #include "mapgen_v6.h"
 
+FlagDesc flagdesc_mapgen[] = {
+	{"trees",          MG_TREES},
+	{"caves",          MG_CAVES},
+	{"dungeons",       MG_DUNGEONS},
+	{"v6_forests",     MGV6_FORESTS},
+	{"v6_biome_blend", MGV6_BIOME_BLEND},
+	{"flat",           MG_FLAT},
+	{NULL,			   0}
+};
 
 ///////////////////////////////////////////////////////////////////////////////
 /////////////////////////////// Emerge Manager ////////////////////////////////
@@ -149,7 +158,7 @@ MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) {
 	mgparams->seed        = settings->getU64(settings == g_settings ? "fixed_map_seed" : "seed");
 	mgparams->water_level = settings->getS16("water_level");
 	mgparams->chunksize   = settings->getS16("chunksize");
-	mgparams->flags       = settings->getS32("mg_flags");
+	mgparams->flags       = settings->getFlagStr("mg_flags", flagdesc_mapgen);
 
 	if (!mgparams->readParams(settings)) {
 		delete mgparams;
@@ -159,7 +168,18 @@ MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) {
 }
 
 
-bool EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) {
+void EmergeManager::setParamsToSettings(Settings *settings) {
+	settings->set("mg_name",         params->mg_name);
+	settings->setU64("seed",         params->seed);
+	settings->setS16("water_level",  params->water_level);
+	settings->setS16("chunksize",    params->chunksize);
+	settings->setFlagStr("mg_flags", params->flags, flagdesc_mapgen);
+
+	params->writeParams(settings);
+}
+
+
+void EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) {
 	mglist.insert(std::make_pair(mgname, mgfactory));
 	infostream << "EmergeManager: registered mapgen " << mgname << std::endl;
 }
diff --git a/src/mapgen.h b/src/mapgen.h
index 728290ffc77000317e980f83387b73c6d2d05da6..4f1ab4ebd1169516327296072a5f21ceb2495d4c 100644
--- a/src/mapgen.h
+++ b/src/mapgen.h
@@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define MG_DUNGEONS      0x04
 #define MGV6_FORESTS     0x08
 #define MGV6_BIOME_BLEND 0x10
+#define MG_FLAT          0x20
 
 class BiomeDefManager;
 class Biome;
@@ -121,8 +122,9 @@ class EmergeManager {
 	Mapgen *getMapgen();
 	void addBlockToQueue();
 	
-	bool registerMapgen(std::string name, MapgenFactory *mgfactory);
+	void registerMapgen(std::string name, MapgenFactory *mgfactory);
 	MapgenParams *getParamsFromSettings(Settings *settings);
+	void setParamsToSettings(Settings *settings);
 	
 	//mapgen helper methods
 	Biome *getBiomeAtPoint(v3s16 p);
diff --git a/src/mapgen_v6.cpp b/src/mapgen_v6.cpp
index 30df1673c9639360b9da4aeed4a125af524b92ba..3a5e1093067b0eb6db1d96d90fce08645de0538c 100644
--- a/src/mapgen_v6.cpp
+++ b/src/mapgen_v6.cpp
@@ -278,6 +278,9 @@ bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos)
 
 double MapgenV6::base_rock_level_2d(u64 seed, v2s16 p)
 {
+	if (flags & MG_FLAT)
+		return water_level;
+	
 	int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
 
 	// The base ground level
@@ -333,6 +336,9 @@ double MapgenV6::base_rock_level_2d(u64 seed, v2s16 p)
 }
 
 double MapgenV6::baseRockLevelFromNoise(v2s16 p) {
+	if (flags & MG_FLAT)
+		return water_level;
+	
 	double base = water_level + 
 		NoisePerlin2DPosOffset(noise_terrain_base->np, p.X, 0.5, p.Y, 0.5, seed);
 	double higher = water_level +
@@ -370,6 +376,9 @@ s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision)
 
 double MapgenV6::get_mud_add_amount(u64 seed, v2s16 p)
 {
+	if (flags & MG_FLAT)
+		return AVERAGE_MUD_AMOUNT;
+		
 	/*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin(
 			0.5+(float)p.X/200, 0.5+(float)p.Y/200,
 			seed+91013, 3, 0.55));*/
@@ -491,34 +500,37 @@ void MapgenV6::makeChunk(BlockMakeData *data)
 		int z = node_min.Z;
 
 		// Need to adjust for the original implementation's +.5 offset...
-		noise_terrain_base->perlinMap2D(
-			x + 0.5 * noise_terrain_base->np->spread.X,
-			z + 0.5 * noise_terrain_base->np->spread.Z);
-		noise_terrain_base->transformNoiseMap();
-
-		noise_terrain_higher->perlinMap2D(
-			x + 0.5 * noise_terrain_higher->np->spread.X,
-			z + 0.5 * noise_terrain_higher->np->spread.Z);
-		noise_terrain_higher->transformNoiseMap();
-
-		noise_steepness->perlinMap2D(
-			x + 0.5 * noise_steepness->np->spread.X,
-			z + 0.5 * noise_steepness->np->spread.Z);
-		noise_steepness->transformNoiseMap();
-
-		noise_height_select->perlinMap2D(
-			x + 0.5 * noise_height_select->np->spread.X,
-			z + 0.5 * noise_height_select->np->spread.Z);
-
+		if (!(flags & MG_FLAT)) {
+			noise_terrain_base->perlinMap2D(
+				x + 0.5 * noise_terrain_base->np->spread.X,
+				z + 0.5 * noise_terrain_base->np->spread.Z);
+			noise_terrain_base->transformNoiseMap();
+
+			noise_terrain_higher->perlinMap2D(
+				x + 0.5 * noise_terrain_higher->np->spread.X,
+				z + 0.5 * noise_terrain_higher->np->spread.Z);
+			noise_terrain_higher->transformNoiseMap();
+
+			noise_steepness->perlinMap2D(
+				x + 0.5 * noise_steepness->np->spread.X,
+				z + 0.5 * noise_steepness->np->spread.Z);
+			noise_steepness->transformNoiseMap();
+
+			noise_height_select->perlinMap2D(
+				x + 0.5 * noise_height_select->np->spread.X,
+				z + 0.5 * noise_height_select->np->spread.Z);
+		}
+		
 		noise_trees->perlinMap2D(
 			x + 0.5 * noise_trees->np->spread.X,
 			z + 0.5 * noise_trees->np->spread.Z);
-
-		noise_mud->perlinMap2D(
-			x + 0.5 * noise_mud->np->spread.X,
-			z + 0.5 * noise_mud->np->spread.Z);
-		noise_mud->transformNoiseMap();
-
+			
+		if (!(flags & MG_FLAT)) {
+			noise_mud->perlinMap2D(
+				x + 0.5 * noise_mud->np->spread.X,
+				z + 0.5 * noise_mud->np->spread.Z);
+			noise_mud->transformNoiseMap();
+		}
 		noise_beach->perlinMap2D(
 			x + 0.2 * noise_beach->np->spread.X,
 			z + 0.7 * noise_beach->np->spread.Z);
diff --git a/src/noise.cpp b/src/noise.cpp
index de9d48808e297ee067631c6bb713e59b2c558c3f..bfb1960c846c426d2d6104a0ea9162422af0e3c6 100644
--- a/src/noise.cpp
+++ b/src/noise.cpp
@@ -367,6 +367,7 @@ void Noise::resizeNoiseBuf(bool is3d) {
  * values from the previous noise lattice as midpoints in the new lattice for the
  * next octave.
  */
+#define idx(x, y) ((y) * nlx + (x))
 void Noise::gradientMap2D(float x, float y, float step_x, float step_y, int seed) {
 	float v00, v01, v10, v11, u, v, orig_u;
 	int index, i, j, x0, y0, noisex, noisey;
@@ -387,25 +388,26 @@ void Noise::gradientMap2D(float x, float y, float step_x, float step_y, int seed
 			noisebuf[index++] = noise2d(x0 + i, y0 + j, seed);
 
 	//calculate interpolations
+	index  = 0;
 	noisey = 0;
 	for (j = 0; j != sy; j++) {
-		v00 = noisebuf[noisey * nlx];
-		v10 = noisebuf[noisey * nlx + 1];
-		v01 = noisebuf[(noisey + 1) * nlx];
-		v11 = noisebuf[(noisey + 1) * nlx + 1];
+		v00 = noisebuf[idx(0, noisey)];
+		v10 = noisebuf[idx(1, noisey)];
+		v01 = noisebuf[idx(0, noisey + 1)];
+		v11 = noisebuf[idx(1, noisey + 1)];
 
 		u = orig_u;
 		noisex = 0;
 		for (i = 0; i != sx; i++) {
-			buf[j * sx + i] = biLinearInterpolation(v00, v10, v01, v11, u, v);
+			buf[index++] = biLinearInterpolation(v00, v10, v01, v11, u, v);
 			u += step_x;
 			if (u >= 1.0) {
 				u -= 1.0;
 				noisex++;
 				v00 = v10;
 				v01 = v11;
-				v10 = noisebuf[noisey * nlx + noisex + 1];
-				v11 = noisebuf[(noisey + 1) * nlx + noisex + 1];
+				v10 = noisebuf[idx(noisex + 1, noisey)];
+				v11 = noisebuf[idx(noisex + 1, noisey + 1)];
 			}
 		}
 
@@ -416,14 +418,16 @@ void Noise::gradientMap2D(float x, float y, float step_x, float step_y, int seed
 		}
 	}
 }
+#undef idx
 
 
+#define idx(x, y, z) ((z) * nly * nlx + (y) * nlx + (x))
 void Noise::gradientMap3D(float x, float y, float z,
 						  float step_x, float step_y, float step_z,
 						  int seed) {
 	float v000, v010, v100, v110;
 	float v001, v011, v101, v111;
-	float u, v, w, orig_u, orig_w;
+	float u, v, w, orig_u, orig_v;
 	int index, i, j, k, x0, y0, z0, noisex, noisey, noisez;
 	int nlx, nly, nlz;
 
@@ -434,49 +438,39 @@ void Noise::gradientMap3D(float x, float y, float z,
 	v = y - (float)y0;
 	w = z - (float)z0;
 	orig_u = u;
-	orig_w = w;
+	orig_v = v;
 
 	//calculate noise point lattice
 	nlx = (int)(u + sx * step_x) + 2;
 	nly = (int)(v + sy * step_y) + 2;
-	nlz = (int)(v + sy * step_z) + 2;
+	nlz = (int)(w + sz * step_z) + 2;
 	index = 0;
 	for (k = 0; k != nlz; k++)
 		for (j = 0; j != nly; j++)
 			for (i = 0; i != nlx; i++)
 				noisebuf[index++] = noise3d(x0 + i, y0 + j, z0 + k, seed);
 
-#define index(x, y, z) ((z) * nly * nlx + (y) * nlx + (x))
-
 	//calculate interpolations
+	index  = 0;
 	noisey = 0;
 	noisez = 0;
 	for (k = 0; k != sz; k++) {
-		v000 = noisebuf[index(0, noisey,     noisez)];
-		v100 = noisebuf[index(1, noisey,     noisez)];
-		v010 = noisebuf[index(0, noisey + 1, noisez)];
-		v110 = noisebuf[index(1, noisey + 1, noisez)];
-		v001 = noisebuf[index(0, noisey,     noisez + 1)];
-		v101 = noisebuf[index(1, noisey,     noisez + 1)];
-		v011 = noisebuf[index(0, noisey + 1, noisez + 1)];
-		v111 = noisebuf[index(1, noisey + 1, noisez + 1)];
-
-		w = orig_w;
+		v = orig_v;
 		noisey = 0;
 		for (j = 0; j != sy; j++) {
-			v000 = noisebuf[index(0, noisey,     noisez)];
-			v100 = noisebuf[index(1, noisey,     noisez)];
-			v010 = noisebuf[index(0, noisey + 1, noisez)];
-			v110 = noisebuf[index(1, noisey + 1, noisez)];
-			v001 = noisebuf[index(0, noisey,     noisez + 1)];
-			v101 = noisebuf[index(1, noisey,     noisez + 1)];
-			v011 = noisebuf[index(0, noisey + 1, noisez + 1)];
-			v111 = noisebuf[index(1, noisey + 1, noisez + 1)];
+			v000 = noisebuf[idx(0, noisey,     noisez)];
+			v100 = noisebuf[idx(1, noisey,     noisez)];
+			v010 = noisebuf[idx(0, noisey + 1, noisez)];
+			v110 = noisebuf[idx(1, noisey + 1, noisez)];
+			v001 = noisebuf[idx(0, noisey,     noisez + 1)];
+			v101 = noisebuf[idx(1, noisey,     noisez + 1)];
+			v011 = noisebuf[idx(0, noisey + 1, noisez + 1)];
+			v111 = noisebuf[idx(1, noisey + 1, noisez + 1)];
 
 			u = orig_u;
 			noisex = 0;
 			for (i = 0; i != sx; i++) {
-				buf[j * sx + i] = triLinearInterpolation(
+				buf[index++] = triLinearInterpolation(
 									v000, v100, v010, v110,
 									v001, v101, v011, v111,
 									u, v, w);
@@ -486,12 +480,12 @@ void Noise::gradientMap3D(float x, float y, float z,
 					noisex++;
 					v000 = v100;
 					v010 = v110;
-					v100 = noisebuf[index(noisex + 1, noisey,     noisez)];
-					v110 = noisebuf[index(noisex + 1, noisey + 1, noisez)];
+					v100 = noisebuf[idx(noisex + 1, noisey,     noisez)];
+					v110 = noisebuf[idx(noisex + 1, noisey + 1, noisez)];
 					v001 = v101;
 					v011 = v111;
-					v101 = noisebuf[index(noisex + 1, noisey,     noisez + 1)];
-					v111 = noisebuf[index(noisex + 1, noisey + 1, noisez + 1)];
+					v101 = noisebuf[idx(noisex + 1, noisey,     noisez + 1)];
+					v111 = noisebuf[idx(noisex + 1, noisey + 1, noisez + 1)];
 				}
 			}
 
@@ -509,6 +503,7 @@ void Noise::gradientMap3D(float x, float y, float z,
 		}
 	}
 }
+#undef idx
 
 
 float *Noise::perlinMap2D(float x, float y) {
diff --git a/src/porting.cpp b/src/porting.cpp
index de15de9ce2309b200a59cb8e80c3e364a093cae1..f8a2cca5c242629d340ad03e6c6c139f987712cd 100644
--- a/src/porting.cpp
+++ b/src/porting.cpp
@@ -220,7 +220,7 @@ void initializePaths()
 	//TODO: Test this code
 	char buf[BUFSIZ];
 	uint32_t len = sizeof(buf);
-	assert(_NSGetExecutablePath(buf, &len) != 0);
+	assert(_NSGetExecutablePath(buf, &len) != -1);
 
 	pathRemoveFile(buf, '/');
 
diff --git a/src/porting.h b/src/porting.h
index c8d19154c9c341fde8b708d4939e7c8e71f82233..9ba3394bbc1d47bec2a0a891fd43c248c00aafc9 100644
--- a/src/porting.h
+++ b/src/porting.h
@@ -56,6 +56,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	#define strtof(x, y) (float)strtod(x, y)
 	#define strtoll(x, y, z) _strtoi64(x, y, z)
 	#define strtoull(x, y, z) _strtoui64(x, y, z)
+	#define strcasecmp(x, y) stricmp(x, y)
 #else
 	#define ALIGNOF(x) __alignof__(x)
 #endif
diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp
index 6e2a0a3140f6d67a3c686e2fd6185ca5cdcf9314..020709cab1cf9966db99710108d9effb0fca6000 100644
--- a/src/scriptapi.cpp
+++ b/src/scriptapi.cpp
@@ -693,6 +693,30 @@ static NodeBox read_nodebox(lua_State *L, int index)
 	return nodebox;
 }
 
+/*
+	NoiseParams
+*/
+static NoiseParams *read_noiseparams(lua_State *L, int index)
+{
+	if (index < 0)
+		index = lua_gettop(L) + 1 + index;
+		
+	if (!lua_istable(L, index))
+		return NULL;
+
+	NoiseParams *np = new NoiseParams;
+		
+	np->offset  = getfloatfield_default(L, index, "offset", 0.0);
+	np->scale   = getfloatfield_default(L, index, "scale", 0.0);
+	lua_getfield(L, index, "spread");
+	np->spread  = read_v3f(L, -1);
+	np->seed    = getintfield_default(L, index, "seed", 0);
+	np->octaves = getintfield_default(L, index, "octaves", 0);
+	np->persist = getfloatfield_default(L, index, "persist", 0.0);
+	
+	return np;
+}
+
 /*
 	Groups
 */
@@ -3241,11 +3265,6 @@ static void objectref_get_or_create(lua_State *L,
 	}
 }
 
-
-/*
-  PerlinNoise
- */
-
 class LuaPerlinNoise
 {
 private:
@@ -3355,6 +3374,145 @@ const luaL_reg LuaPerlinNoise::methods[] = {
 	{0,0}
 };
 
+/*
+  PerlinNoiseMap
+ */
+class LuaPerlinNoiseMap
+{
+private:
+	Noise *noise;
+	static const char className[];
+	static const luaL_reg methods[];
+
+	static int gc_object(lua_State *L)
+	{
+		LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)(lua_touserdata(L, 1));
+		delete o;
+		return 0;
+	}
+
+	static int l_get2dMap(lua_State *L)
+	{
+		int i = 0;
+
+		LuaPerlinNoiseMap *o = checkobject(L, 1);
+		v2f p = read_v2f(L, 2);
+		
+		Noise *n = o->noise;
+		n->perlinMap2D(p.X, p.Y);
+		
+		lua_newtable(L);
+		for (int y = 0; y != n->sy; y++) {
+			lua_newtable(L);
+			for (int x = 0; x != n->sx; x++) {
+				float noiseval = n->np->offset + n->np->scale * n->result[i++];
+				lua_pushnumber(L, noiseval);
+				lua_rawseti(L, -2, x + 1);
+			}
+			lua_rawseti(L, -2, y + 1);
+		}
+		return 1;
+	}
+	
+	static int l_get3dMap(lua_State *L)
+	{
+		int i = 0;
+		
+		LuaPerlinNoiseMap *o = checkobject(L, 1);
+		v3f p = read_v3f(L, 2);
+		
+		Noise *n = o->noise;
+		n->perlinMap3D(p.X, p.Y, p.Z);
+
+		lua_newtable(L);
+		for (int z = 0; z != n->sz; z++) {
+			lua_newtable(L);
+			for (int y = 0; y != n->sy; y++) {
+				lua_newtable(L);
+				for (int x = 0; x != n->sx; x++) {
+					lua_pushnumber(L, n->np->offset + n->np->scale * n->result[i++]);
+					lua_rawseti(L, -2, x + 1);
+				}
+				lua_rawseti(L, -2, y + 1);
+			}
+			lua_rawseti(L, -2, z + 1);
+		}
+		return 1;
+	}
+
+public:
+	LuaPerlinNoiseMap(NoiseParams *np, int seed, v3s16 size) {
+		noise = new Noise(np, seed, size.X, size.Y, size.Z);
+	}
+
+	~LuaPerlinNoiseMap()
+	{
+		delete noise->np;
+		delete noise;
+	}
+
+	// LuaPerlinNoiseMap(np, size)
+	// Creates an LuaPerlinNoiseMap and leaves it on top of stack
+	static int create_object(lua_State *L)
+	{
+		NoiseParams *np = read_noiseparams(L, 1);
+		if (!np)
+			return 0;
+		v3s16 size = read_v3s16(L, 2);
+
+		LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(np, 0, size);
+		*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
+		luaL_getmetatable(L, className);
+		lua_setmetatable(L, -2);
+		return 1;
+	}
+
+	static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg)
+	{
+		luaL_checktype(L, narg, LUA_TUSERDATA);
+		
+		void *ud = luaL_checkudata(L, narg, className);
+		if (!ud)
+			luaL_typerror(L, narg, className);
+		
+		return *(LuaPerlinNoiseMap **)ud;  // unbox pointer
+	}
+
+	static void Register(lua_State *L)
+	{
+		lua_newtable(L);
+		int methodtable = lua_gettop(L);
+		luaL_newmetatable(L, className);
+		int metatable = lua_gettop(L);
+
+		lua_pushliteral(L, "__metatable");
+		lua_pushvalue(L, methodtable);
+		lua_settable(L, metatable);  // hide metatable from Lua getmetatable()
+
+		lua_pushliteral(L, "__index");
+		lua_pushvalue(L, methodtable);
+		lua_settable(L, metatable);
+
+		lua_pushliteral(L, "__gc");
+		lua_pushcfunction(L, gc_object);
+		lua_settable(L, metatable);
+
+		lua_pop(L, 1);  // drop metatable
+
+		luaL_openlib(L, 0, methods, 0);  // fill methodtable
+		lua_pop(L, 1);  // drop methodtable
+
+		// Can be created from Lua (PerlinNoiseMap(np, size)
+		lua_register(L, className, create_object);
+	}
+};
+const char LuaPerlinNoiseMap::className[] = "PerlinNoiseMap";
+const luaL_reg LuaPerlinNoiseMap::methods[] = {
+	method(LuaPerlinNoiseMap, get2dMap),
+	method(LuaPerlinNoiseMap, get3dMap),
+	{0,0}
+};
+
 /*
 	NodeTimerRef
 */
@@ -4019,6 +4177,28 @@ class EnvRef
 		lua_setmetatable(L, -2);
 		return 1;
 	}
+    
+	//  EnvRef:get_perlin_map(noiseparams, size)
+	//  returns world-specific PerlinNoiseMap
+	static int l_get_perlin_map(lua_State *L)
+	{
+		EnvRef *o = checkobject(L, 1);
+		ServerEnvironment *env = o->m_env;
+		if (env == NULL)
+			return 0;
+		
+		NoiseParams *np = read_noiseparams(L, 2);
+		if (!np)
+			return 0;
+		v3s16 size = read_v3s16(L, 3);
+		
+		int seed = (int)(env->getServerMap().getSeed());
+		LuaPerlinNoiseMap *n = new LuaPerlinNoiseMap(np, seed, size);
+		*(void **)(lua_newuserdata(L, sizeof(void *))) = n;
+		luaL_getmetatable(L, "PerlinNoiseMap");
+		lua_setmetatable(L, -2);
+		return 1;
+	}
 
 	// EnvRef:clear_objects()
 	// clear all objects in the environment
@@ -4158,8 +4338,7 @@ const luaL_reg EnvRef::methods[] = {
 	method(EnvRef, find_node_near),
 	method(EnvRef, find_nodes_in_area),
 	method(EnvRef, get_perlin),
-	//method{EnvRef, get_perlin_map_2d},
-	//method{EnvRef, get_perlin_map_3d},
+	method(EnvRef, get_perlin_map),
 	method(EnvRef, clear_objects),
 	method(EnvRef, spawn_tree),
 	{0,0}
@@ -5424,6 +5603,7 @@ void scriptapi_export(lua_State *L, Server *server)
 	EnvRef::Register(L);
 	LuaPseudoRandom::Register(L);
 	LuaPerlinNoise::Register(L);
+	LuaPerlinNoiseMap::Register(L);
 }
 
 bool scriptapi_loadmod(lua_State *L, const std::string &scriptpath,
diff --git a/src/settings.h b/src/settings.h
index 2b46676c6b7ae03a440b2a9870f4494b2599a51f..addd9980cb67d71a31e72bf049d114abb62ecd04 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -569,6 +569,12 @@ class Settings
 		return value;
 	}
 
+	u32 getFlagStr(std::string name, FlagDesc *flagdesc)
+	{
+		std::string val = get(name);
+		return (isdigit(val[0])) ? stoi(val) : readFlagString(val, flagdesc);
+	}
+
 	template <class T> T *getStruct(std::string name, std::string format)
 	{
 		size_t len = sizeof(T);
@@ -831,6 +837,11 @@ class Settings
 		set(name, std::string(sbuf));
 		return true;
 	}
+	
+	void setFlagStr(std::string name, u32 flags, FlagDesc *flagdesc)
+	{
+		set(name, writeFlagString(flags, flagdesc));
+	}
 
 	void setBool(std::string name, bool value)
 	{
diff --git a/src/util/string.cpp b/src/util/string.cpp
index c10755ae1dba68d47c314ab9821ead0d06bbb54b..61b307c60e7d678a15f0f371308858354a67238b 100644
--- a/src/util/string.cpp
+++ b/src/util/string.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "../sha1.h"
 #include "../base64.h"
+#include "../porting.h"
 
 // Get an sha-1 hash of the player's name combined with
 // the password entered. That's what the server uses as
@@ -48,6 +49,45 @@ size_t curl_write_data(char *ptr, size_t size, size_t nmemb, void *userdata) {
     return count;
 }
 
+u32 readFlagString(std::string str, FlagDesc *flagdesc) {
+	u32 result = 0;
+	char *s = &str[0];
+	char *flagstr, *strpos = NULL;
+	
+	while ((flagstr = strtok_r(s, ",", &strpos))) {
+		s = NULL;
+		
+		while (*flagstr == ' ' || *flagstr == '\t')
+			flagstr++;
+		
+		for (int i = 0; flagdesc[i].name; i++) {
+			if (!strcasecmp(flagstr, flagdesc[i].name)) {
+				result |= flagdesc[i].flag;
+				break;
+			}
+		}
+	}
+	
+	return result;
+}
+
+std::string writeFlagString(u32 flags, FlagDesc *flagdesc) {
+	std::string result;
+	
+	for (int i = 0; flagdesc[i].name; i++) {
+		if (flags & flagdesc[i].flag) {
+			result += flagdesc[i].name;
+			result += ", ";
+		}
+	}
+	
+	size_t len = result.length();
+	if (len >= 2)
+		result.erase(len - 2, 2);
+	
+	return result;
+}
+
 char *mystrtok_r(char *s, const char *sep, char **lasts) {
 	char *t;
 
diff --git a/src/util/string.h b/src/util/string.h
index d081b365b42fd837846dedfaa84208c28d32a6a5..a469a074a2b90d31c010cf0a8fcc2a8c5163d5e3 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -28,6 +28,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <vector>
 #include <sstream>
 
+struct FlagDesc {
+	const char *name;
+	u32 flag;
+};
+
 static inline std::string padStringRight(std::string s, size_t len)
 {
 	if(len > s.size())
@@ -283,6 +288,8 @@ inline std::string wrap_rows(const std::string &from, u32 rowlen)
 
 std::string translatePassword(std::string playername, std::wstring password);
 size_t curl_write_data(char *ptr, size_t size, size_t nmemb, void *userdata);
+u32 readFlagString(std::string str, FlagDesc *flagdesc);
+std::string writeFlagString(u32 flags, FlagDesc *flagdesc);
 char *mystrtok_r(char *s, const char *sep, char **lasts);
 
 #endif