diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index ddd7b5fe12f15f2001686ee3eba8ef882f2cc1fb..9578579765d59767eaad9aef05ed959bb2cc560a 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -247,7 +247,6 @@ void set_default_settings(Settings *settings)
 	settings->setDefault("default_privs", "interact, shout");
 	settings->setDefault("player_transfer_distance", "0");
 	settings->setDefault("enable_pvp", "true");
-	settings->setDefault("vertical_spawn_range", "16");
 	settings->setDefault("disallow_empty_password", "false");
 	settings->setDefault("disable_anticheat", "false");
 	settings->setDefault("enable_rollback_recording", "false");
diff --git a/src/emerge.cpp b/src/emerge.cpp
index ccb4c1703554341f0b87a8475514761a0a9baabe..93e8f2b30a92abcbbd81f226b75ae00e8d5bb546 100644
--- a/src/emerge.cpp
+++ b/src/emerge.cpp
@@ -334,6 +334,18 @@ v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize)
 }
 
 
+int EmergeManager::getSpawnLevelAtPoint(v2s16 p)
+{
+	if (m_mapgens.size() == 0 || !m_mapgens[0]) {
+		errorstream << "EmergeManager: getSpawnLevelAtPoint() called"
+			" before mapgen init" << std::endl;
+		return 0;
+	}
+
+	return m_mapgens[0]->getSpawnLevelAtPoint(p);
+}
+
+
 int EmergeManager::getGroundLevelAtPoint(v2s16 p)
 {
 	if (m_mapgens.size() == 0 || !m_mapgens[0]) {
diff --git a/src/emerge.h b/src/emerge.h
index 02bdf7e672de1bfc4ef58e41dd3f8df5f3bf3aad..825ac1c0fbf490a9204a687a0a2c34e9d1c5cab2 100644
--- a/src/emerge.h
+++ b/src/emerge.h
@@ -136,6 +136,7 @@ class EmergeManager {
 
 	// Mapgen helpers methods
 	Biome *getBiomeAtPoint(v3s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 	int getGroundLevelAtPoint(v2s16 p);
 	bool isBlockUnderground(v3s16 blockpos);
 
diff --git a/src/mapgen.h b/src/mapgen.h
index 9bb7d03b89241a9449c237498731476c73a6e3d6..abc3d2e899af04d3bdc8027edf9ea827014e47d9 100644
--- a/src/mapgen.h
+++ b/src/mapgen.h
@@ -181,6 +181,13 @@ class Mapgen {
 	virtual void makeChunk(BlockMakeData *data) {}
 	virtual int getGroundLevelAtPoint(v2s16 p) { return 0; }
 
+	// getSpawnLevelAtPoint() is a function within each mapgen that returns a
+	// suitable y co-ordinate for player spawn ('suitable' usually meaning
+	// within 16 nodes of water_level). If a suitable spawn level cannot be
+	// found at the specified (X, Z) 'MAX_MAP_GENERATION_LIMIT' is returned to
+	// signify this and to cause Server::findSpawnPos() to try another (X, Z).
+	virtual int getSpawnLevelAtPoint(v2s16 p) { return 0; }
+
 private:
 	DISABLE_CLASS_COPY(Mapgen);
 };
diff --git a/src/mapgen_flat.cpp b/src/mapgen_flat.cpp
index 3b7178dd76922143b79252a22a3cd36ac485f9bb..0d071411d75a5a376511804c90e035f656003ffa 100644
--- a/src/mapgen_flat.cpp
+++ b/src/mapgen_flat.cpp
@@ -192,18 +192,25 @@ void MapgenFlatParams::writeParams(Settings *settings) const
 /////////////////////////////////////////////////////////////////
 
 
-int MapgenFlat::getGroundLevelAtPoint(v2s16 p)
+int MapgenFlat::getSpawnLevelAtPoint(v2s16 p)
 {
+	s16 level_at_point = ground_level;
 	float n_terrain = NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed);
+
 	if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
-		s16 depress = (lake_threshold - n_terrain) * lake_steepness;
-		return ground_level - depress;
+		level_at_point = ground_level -
+			(lake_threshold - n_terrain) * lake_steepness;
 	} else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
-		s16 rise = (n_terrain - hill_threshold) * hill_steepness;
-		return ground_level + rise;
-	} else {
-		return ground_level;
+		level_at_point = ground_level +
+			(n_terrain - hill_threshold) * hill_steepness;
 	}
+
+	if (ground_level < water_level)  // Ocean world, allow spawn in water
+		return MYMAX(level_at_point, water_level);
+	else if (level_at_point > water_level)
+		return level_at_point;  // Spawn on land
+	else
+		return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
 }
 
 
diff --git a/src/mapgen_flat.h b/src/mapgen_flat.h
index 0e15df781b20f7ef9a3c0215b67c9609b2f7d596..282e4297511482e493583e2770355e368100f0c7 100644
--- a/src/mapgen_flat.h
+++ b/src/mapgen_flat.h
@@ -102,7 +102,7 @@ class MapgenFlat : public Mapgen {
 	~MapgenFlat();
 
 	virtual void makeChunk(BlockMakeData *data);
-	int getGroundLevelAtPoint(v2s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 	void calculateNoise();
 	s16 generateTerrain();
 	MgStoneType generateBiomes(float *heat_map, float *humidity_map);
diff --git a/src/mapgen_fractal.cpp b/src/mapgen_fractal.cpp
index 9cb682a911d04402862014af4ea6947ba03f1d2a..8a5c1e2bc8dde8b2d87f80d6c305880f15d6f9a7 100644
--- a/src/mapgen_fractal.cpp
+++ b/src/mapgen_fractal.cpp
@@ -209,17 +209,28 @@ void MapgenFractalParams::writeParams(Settings *settings) const
 /////////////////////////////////////////////////////////////////
 
 
-int MapgenFractal::getGroundLevelAtPoint(v2s16 p)
+int MapgenFractal::getSpawnLevelAtPoint(v2s16 p)
 {
-	s16 search_start = 128;
-	s16 search_end = -128;
-
-	for (s16 y = search_start; y >= search_end; y--) {
-		if (getFractalAtPoint(p.X, y, p.Y))
-			return y;
+	bool solid_below = false;  // Dry solid node is present below to spawn on
+	u8 air_count = 0;  // Consecutive air nodes above the dry solid node
+	s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed);
+	// Seabed can rise above water_level or might be raised to create dry land
+	s16 search_start = MYMAX(seabed_level, water_level + 1);
+	if (seabed_level > water_level)
+		solid_below = true;
+		
+	for (s16 y = search_start; y <= search_start + 128; y++) {
+		if (getFractalAtPoint(p.X, y, p.Y)) {  // Fractal node
+			solid_below = true;
+			air_count = 0;
+		} else if (solid_below) {  // Air above solid node
+			air_count++;
+			if (air_count == 2)
+				return y - 2;
+		}
 	}
 
-	return -MAX_MAP_GENERATION_LIMIT;
+	return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
 }
 
 
diff --git a/src/mapgen_fractal.h b/src/mapgen_fractal.h
index 3d4f7ee8ff9620bd95961744627ed77f7d45c3c8..ffb2acfb9ab40530712c1deb94988f7b4ee024ce 100644
--- a/src/mapgen_fractal.h
+++ b/src/mapgen_fractal.h
@@ -114,7 +114,7 @@ class MapgenFractal : public Mapgen {
 	~MapgenFractal();
 
 	virtual void makeChunk(BlockMakeData *data);
-	int getGroundLevelAtPoint(v2s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 	void calculateNoise();
 	bool getFractalAtPoint(s16 x, s16 y, s16 z);
 	s16 generateTerrain();
diff --git a/src/mapgen_singlenode.cpp b/src/mapgen_singlenode.cpp
index f87115269e3f0771ae47551d1a09f57b1d72d05f..ff985dd34446056bff682535b9ab1950cefc6824 100644
--- a/src/mapgen_singlenode.cpp
+++ b/src/mapgen_singlenode.cpp
@@ -99,7 +99,7 @@ void MapgenSinglenode::makeChunk(BlockMakeData *data)
 }
 
 
-int MapgenSinglenode::getGroundLevelAtPoint(v2s16 p)
+int MapgenSinglenode::getSpawnLevelAtPoint(v2s16 p)
 {
 	return 0;
 }
diff --git a/src/mapgen_singlenode.h b/src/mapgen_singlenode.h
index f9c97b50874554718f26152982b9b29a4335583b..2c6496c00693f49139d06d9feb641caa8b769853 100644
--- a/src/mapgen_singlenode.h
+++ b/src/mapgen_singlenode.h
@@ -41,7 +41,7 @@ class MapgenSinglenode : public Mapgen {
 	~MapgenSinglenode();
 	
 	void makeChunk(BlockMakeData *data);
-	int getGroundLevelAtPoint(v2s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 };
 
 struct MapgenFactorySinglenode : public MapgenFactory {
diff --git a/src/mapgen_v5.cpp b/src/mapgen_v5.cpp
index 06e5f1ca60df77d98de7ea578485e3ed3b98eb4c..0bb3715a85337773a3a5b2ac2d5d64be4ecb42c9 100644
--- a/src/mapgen_v5.cpp
+++ b/src/mapgen_v5.cpp
@@ -171,7 +171,7 @@ void MapgenV5Params::writeParams(Settings *settings) const
 }
 
 
-int MapgenV5::getGroundLevelAtPoint(v2s16 p)
+int MapgenV5::getSpawnLevelAtPoint(v2s16 p)
 {
 	//TimeTaker t("getGroundLevelAtPoint", NULL, PRECISION_MICRO);
 
@@ -182,24 +182,25 @@ int MapgenV5::getGroundLevelAtPoint(v2s16 p)
 		f *= 1.6;
 	float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed);
 
-	s16 search_start = 128; // Only bother searching this range, actual
-	s16 search_end = -128;  // ground level is rarely higher or lower.
-
-	for (s16 y = search_start; y >= search_end; y--) {
+	for (s16 y = 128; y >= -128; y--) {
 		float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed);
-		// If solid
-		if (n_ground * f > y - h) {
+
+		if (n_ground * f > y - h) {  // If solid
 			// If either top 2 nodes of search are solid this is inside a
-			// mountain or floatland with no space for the player to spawn.
-			if (y >= search_start - 1)
-				return MAX_MAP_GENERATION_LIMIT;
-			else
-				return y; // Ground below at least 2 nodes of space
+			// mountain or floatland with possibly no space for the player to spawn.
+			if (y >= 127) {
+				return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+			} else {  // Ground below at least 2 nodes of empty space
+				if (y <= water_level || y > water_level + 16)
+					return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+				else
+					return y;
+			}
 		}
 	}
 
 	//printf("getGroundLevelAtPoint: %dus\n", t.stop());
-	return -MAX_MAP_GENERATION_LIMIT;
+	return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn position, no ground found
 }
 
 
diff --git a/src/mapgen_v5.h b/src/mapgen_v5.h
index 4112bbfa61135db551bba763ac3788eba8913b2e..44a06907da0ac25814a0d89f4bada63d90f3d68b 100644
--- a/src/mapgen_v5.h
+++ b/src/mapgen_v5.h
@@ -90,7 +90,7 @@ class MapgenV5 : public Mapgen {
 	~MapgenV5();
 
 	virtual void makeChunk(BlockMakeData *data);
-	int getGroundLevelAtPoint(v2s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 	void calculateNoise();
 	int generateBaseTerrain();
 	MgStoneType generateBiomes(float *heat_map, float *humidity_map);
diff --git a/src/mapgen_v6.cpp b/src/mapgen_v6.cpp
index 57d0f59b3401e685a9da5899032c1e0af931d8b2..e24083cffb3b71d5092227633d7de2abf4959e8e 100644
--- a/src/mapgen_v6.cpp
+++ b/src/mapgen_v6.cpp
@@ -318,6 +318,17 @@ int MapgenV6::getGroundLevelAtPoint(v2s16 p)
 }
 
 
+int MapgenV6::getSpawnLevelAtPoint(v2s16 p)
+{
+	s16 level_at_point = baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
+	if (level_at_point <= water_level ||
+			level_at_point > water_level + 16)
+		return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+	else
+		return level_at_point;
+}
+
+
 //////////////////////// Noise functions
 
 float MapgenV6::getMudAmount(v2s16 p)
diff --git a/src/mapgen_v6.h b/src/mapgen_v6.h
index 612e2322ab32f226dd798062450efc88a4e11738..a55fc6d534083dd458a6f38f26f1b05692c65397 100644
--- a/src/mapgen_v6.h
+++ b/src/mapgen_v6.h
@@ -129,6 +129,7 @@ class MapgenV6 : public Mapgen {
 
 	void makeChunk(BlockMakeData *data);
 	int getGroundLevelAtPoint(v2s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 
 	float baseTerrainLevel(float terrain_base, float terrain_higher,
 		float steepness, float height_select);
diff --git a/src/mapgen_v7.cpp b/src/mapgen_v7.cpp
index aec00b938c279f704ec74e5a562377f437421f5b..029f56a45a74d3d9b8f38dcbaa7024ab552d065e 100644
--- a/src/mapgen_v7.cpp
+++ b/src/mapgen_v7.cpp
@@ -202,7 +202,7 @@ void MapgenV7Params::writeParams(Settings *settings) const
 ///////////////////////////////////////
 
 
-int MapgenV7::getGroundLevelAtPoint(v2s16 p)
+int MapgenV7::getSpawnLevelAtPoint(v2s16 p)
 {
 	// Base terrain calculation
 	s16 y = baseTerrainLevelAtPoint(p.X, p.Y);
@@ -210,22 +210,24 @@ int MapgenV7::getGroundLevelAtPoint(v2s16 p)
 	// Ridge/river terrain calculation
 	float width = 0.2;
 	float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2;
-	// actually computing the depth of the ridge is much more expensive;
-	// if inside a river, simply guess
+	// if inside a river this is an unsuitable spawn point
 	if (fabs(uwatern) <= width)
-		return water_level - 10;
+		return MAX_MAP_GENERATION_LIMIT;
 
 	// Mountain terrain calculation
-	int iters = 128; // don't even bother iterating more than 128 times..
+	int iters = 128;
 	while (iters--) {
-		//current point would have been air
-		if (!getMountainTerrainAtPoint(p.X, y, p.Y))
-			return y;
-
+		if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) {  // Air, y is ground level
+			if (y <= water_level || y > water_level + 16)
+				return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+			else
+				return y;
+		}
 		y++;
 	}
 
-	return y;
+	// Unsuitable spawn point, no ground surface found
+	return MAX_MAP_GENERATION_LIMIT;
 }
 
 
diff --git a/src/mapgen_v7.h b/src/mapgen_v7.h
index 9b483b0410ee62f7b8936ae84b7f19e7f5c43269..82f89387b1aa79ec5adc21ecdb2ea7497a626de6 100644
--- a/src/mapgen_v7.h
+++ b/src/mapgen_v7.h
@@ -103,7 +103,7 @@ class MapgenV7 : public Mapgen {
 	~MapgenV7();
 
 	virtual void makeChunk(BlockMakeData *data);
-	int getGroundLevelAtPoint(v2s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 	Biome *getBiomeAtPoint(v3s16 p);
 
 	float baseTerrainLevelAtPoint(s16 x, s16 z);
diff --git a/src/mapgen_valleys.cpp b/src/mapgen_valleys.cpp
index ceb2c774d988f693ac1f14260b7c5186a012f49a..2f96c339773f673a4461129ac84aa5211d8df921 100644
--- a/src/mapgen_valleys.cpp
+++ b/src/mapgen_valleys.cpp
@@ -56,8 +56,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 //Profiler *mapgen_profiler = &mapgen_prof;
 
 static FlagDesc flagdesc_mapgen_valleys[] = {
-	{"altitude_chill", MG_VALLEYS_ALT_CHILL},
-	{"humid_rivers",   MG_VALLEYS_HUMID_RIVERS},
+	{"altitude_chill", MGVALLEYS_ALT_CHILL},
+	{"humid_rivers",   MGVALLEYS_HUMID_RIVERS},
 	{NULL,             0}
 };
 
@@ -86,8 +86,8 @@ MapgenValleys::MapgenValleys(int mapgenid, MapgenParams *params, EmergeManager *
 	MapgenValleysParams *sp = (MapgenValleysParams *)params->sparams;
 	this->spflags = sp->spflags;
 
-	this->humid_rivers       = (spflags & MG_VALLEYS_HUMID_RIVERS);
-	this->use_altitude_chill = (spflags & MG_VALLEYS_ALT_CHILL);
+	this->humid_rivers       = (spflags & MGVALLEYS_HUMID_RIVERS);
+	this->use_altitude_chill = (spflags & MGVALLEYS_ALT_CHILL);
 
 	this->altitude_chill     = sp->altitude_chill;
 	this->humidity_adjust    = params->np_biome_humidity.offset - 50.f;
@@ -181,7 +181,7 @@ MapgenValleys::~MapgenValleys()
 
 MapgenValleysParams::MapgenValleysParams()
 {
-	spflags = MG_VALLEYS_HUMID_RIVERS | MG_VALLEYS_ALT_CHILL;
+	spflags = MGVALLEYS_HUMID_RIVERS | MGVALLEYS_ALT_CHILL;
 
 	altitude_chill     = 90; // The altitude at which temperature drops by 20C.
 	large_cave_depth   = -33;
@@ -513,24 +513,19 @@ float MapgenValleys::adjustedTerrainLevelFromNoise(TerrainNoise *tn)
 }
 
 
-int MapgenValleys::getGroundLevelAtPoint(v2s16 p)
+int MapgenValleys::getSpawnLevelAtPoint(v2s16 p)
 {
-	// ***********************************
-	// This method (deliberately) does not
-	// return correct terrain values.
-	// ***********************************
-
-	// Since MT doesn't normally deal with rivers, check
-	// to make sure this isn't a request for a location
-	// in a river.
+	// Check to make sure this isn't a request for a location in a river.
 	float rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed);
-
-	// If it's wet, return an unusable number.
 	if (fabs(rivers) < river_size_factor)
-		return MAX_MAP_GENERATION_LIMIT;
-
-	// Otherwise, return the real result.
-	return terrainLevelAtPoint(p.X, p.Y);
+		return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+
+	s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
+	if (level_at_point <= water_level ||
+			level_at_point > water_level + 16)
+		return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+	else
+		return level_at_point;
 }
 
 
diff --git a/src/mapgen_valleys.h b/src/mapgen_valleys.h
index ee4052d716b0d974cb731b0da5510c865c343350..6b3eb9cfe87aa478610bf808e97d3b56269d469c 100644
--- a/src/mapgen_valleys.h
+++ b/src/mapgen_valleys.h
@@ -30,9 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "mapgen.h"
 
-/////////////////// Mapgen Valleys flags
-#define MG_VALLEYS_ALT_CHILL    0x01
-#define MG_VALLEYS_HUMID_RIVERS 0x02
+////////////// Mapgen Valleys flags
+#define MGVALLEYS_ALT_CHILL    0x01
+#define MGVALLEYS_HUMID_RIVERS 0x02
 
 // Feed only one variable into these.
 #define MYSQUARE(x) (x) * (x)
@@ -96,7 +96,7 @@ class MapgenValleys : public Mapgen {
 	~MapgenValleys();
 
 	virtual void makeChunk(BlockMakeData *data);
-	int getGroundLevelAtPoint(v2s16 p);
+	int getSpawnLevelAtPoint(v2s16 p);
 
 	s16 large_cave_depth;
 
diff --git a/src/server.cpp b/src/server.cpp
index 86096055d1f5e9fd1114dc69686522f81be31b53..572533146555d9d923b17aed3e52297634978773 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -3383,26 +3383,24 @@ v3f Server::findSpawnPos()
 		return nodeposf * BS;
 	}
 
-	s16 water_level = map.getWaterLevel();
-	s16 vertical_spawn_range = g_settings->getS16("vertical_spawn_range");
 	bool is_good = false;
 
 	// Try to find a good place a few times
-	for(s32 i = 0; i < 1000 && !is_good; i++) {
+	for(s32 i = 0; i < 4000 && !is_good; i++) {
 		s32 range = 1 + i;
 		// We're going to try to throw the player to this position
 		v2s16 nodepos2d = v2s16(
 				-range + (myrand() % (range * 2)),
 				-range + (myrand() % (range * 2)));
 
-		// Get ground height at point
-		s16 groundheight = map.findGroundLevel(nodepos2d);
-		// Don't go underwater or to high places
-		if (groundheight <= water_level ||
-				groundheight > water_level + vertical_spawn_range)
+		// Get spawn level at point
+		s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
+		// Continue if MAX_MAP_GENERATION_LIMIT was returned by
+		// the mapgen to signify an unsuitable spawn position
+		if (spawn_level == MAX_MAP_GENERATION_LIMIT)
 			continue;
 
-		v3s16 nodepos(nodepos2d.X, groundheight, nodepos2d.Y);
+		v3s16 nodepos(nodepos2d.X, spawn_level, nodepos2d.Y);
 
 		s32 air_count = 0;
 		for (s32 i = 0; i < 10; i++) {