diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp
index 9ec7c7a64..47272142f 100644
--- a/src/mapgen/cavegen.cpp
+++ b/src/mapgen/cavegen.cpp
@@ -38,14 +38,16 @@ static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0
 ////
 
 CavesNoiseIntersection::CavesNoiseIntersection(
-	const NodeDefManager *nodedef, BiomeManager *biomemgr, v3s16 chunksize,
+	const NodeDefManager *nodedef, BiomeManager *biomemgr, BiomeGen *biomegen, v3s16 chunksize,
 	NoiseParams *np_cave1, NoiseParams *np_cave2, s32 seed, float cave_width)
 {
 	assert(nodedef);
 	assert(biomemgr);
+	assert(biomegen);
 
 	m_ndef = nodedef;
 	m_bmgr = biomemgr;
+	m_bmgn = biomegen;
 
 	m_csize = chunksize;
 	m_cave_width = cave_width;
@@ -80,6 +82,8 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
 	const v3s16 &em = vm->m_area.getExtent();
 	u32 index2d = 0;  // Biomemap index
 
+	s16 *biome_transitions = m_bmgn->getBiomeTransitions();
+
 	for (s16 z = nmin.Z; z <= nmax.Z; z++)
 	for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
 		bool column_is_open = false;  // Is column open to overground
@@ -96,6 +100,10 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
 		u16 base_filler = depth_top + biome->depth_filler;
 		u16 depth_riverbed = biome->depth_riverbed;
 		u16 nplaced = 0;
+
+		int cur_biome_depth = 0;
+		s16 biome_y_min = biome_transitions[cur_biome_depth];
+
 		// Don't excavate the overgenerated stone at nmax.Y + 1,
 		// this creates a 'roof' over the tunnel, preventing light in
 		// tunnels at mapchunk borders when generating mapchunks upwards.
@@ -103,6 +111,20 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
 		for (s16 y = nmax.Y; y >= nmin.Y - 1; y--,
 				index3d -= m_ystride,
 				VoxelArea::add_y(em, vi, -1)) {
+			// We need this check to make sure that biomes don't generate too far down
+			if (y < biome_y_min) {
+				biome = m_bmgn->getBiomeAtIndex(index2d, v3s16(x, y, z));
+
+				// Finding the height of the next biome
+				// On first iteration this may loop a couple times after than it should just run once
+				while (y < biome_y_min) {
+					biome_y_min = biome_transitions[++cur_biome_depth];
+				}
+
+				/*if (x == nmin.X && z == nmin.Z)
+					printf("Cave: check @ %i -> %s -> again at %i\n", y, biome->name.c_str(), biome_y_min);*/
+			}
+
 			content_t c = vm->m_data[vi].getContent();
 
 			if (c == CONTENT_AIR || c == biome->c_water_top ||
diff --git a/src/mapgen/cavegen.h b/src/mapgen/cavegen.h
index 6bc267199..5cf2e530e 100644
--- a/src/mapgen/cavegen.h
+++ b/src/mapgen/cavegen.h
@@ -26,6 +26,8 @@ typedef u16 biome_t;  // copy from mg_biome.h to avoid an unnecessary include
 
 class GenerateNotifier;
 
+class BiomeGen;
+
 /*
 	CavesNoiseIntersection is a cave digging algorithm that carves smooth,
 	web-like, continuous tunnels at points where the density of the intersection
@@ -42,7 +44,7 @@ class CavesNoiseIntersection
 {
 public:
 	CavesNoiseIntersection(const NodeDefManager *nodedef,
-		BiomeManager *biomemgr, v3s16 chunksize, NoiseParams *np_cave1,
+		BiomeManager *biomemgr, BiomeGen *biomegen, v3s16 chunksize, NoiseParams *np_cave1,
 		NoiseParams *np_cave2, s32 seed, float cave_width);
 	~CavesNoiseIntersection();
 
@@ -52,6 +54,8 @@ private:
 	const NodeDefManager *m_ndef;
 	BiomeManager *m_bmgr;
 
+	BiomeGen *m_bmgn;
+
 	// configurable parameters
 	v3s16 m_csize;
 	float m_cave_width;
diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp
index ce281e2c1..f4879078a 100644
--- a/src/mapgen/mapgen.cpp
+++ b/src/mapgen/mapgen.cpp
@@ -636,6 +636,8 @@ void MapgenBasic::generateBiomes()
 
 	noise_filler_depth->perlinMap2D(node_min.X, node_min.Z);
 
+	s16 *biome_transitions = biomegen->getBiomeTransitions();
+
 	for (s16 z = node_min.Z; z <= node_max.Z; z++)
 	for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
 		Biome *biome = NULL;
@@ -644,9 +646,11 @@ void MapgenBasic::generateBiomes()
 		u16 base_filler = 0;
 		u16 depth_water_top = 0;
 		u16 depth_riverbed = 0;
-		s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT;
 		u32 vi = vm->m_area.index(x, node_max.Y, z);
 
+		int cur_biome_depth = 0;
+		s16 biome_y_min = biome_transitions[cur_biome_depth];
+
 		// Check node at base of mapchunk above, either a node of a previously
 		// generated mapchunk or if not, a node of overgenerated base terrain.
 		content_t c_above = vm->m_data[vi + em.X].getContent();
@@ -675,8 +679,19 @@ void MapgenBasic::generateBiomes()
 				(air_above || !biome || y < biome_y_min); // 2, 3, 4
 
 			if (is_stone_surface || is_water_surface) {
-				// (Re)calculate biome
-				biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z));
+				if (!biome || y < biome_y_min) {
+					// (Re)calculate biome
+					biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z));
+
+					// Finding the height of the next biome
+					// On first iteration this may loop a couple times after than it should just run once
+					while (y < biome_y_min) {
+						biome_y_min = biome_transitions[++cur_biome_depth];
+					}
+
+					/*if (x == node_min.X && z == node_min.Z)
+						printf("Map: check @ %i -> %s -> again at %i\n", y, biome->name.c_str(), biome_y_min);*/
+				}
 
 				// Add biome to biomemap at first stone surface detected
 				if (biomemap[index] == BIOME_NONE && is_stone_surface)
@@ -693,7 +708,6 @@ void MapgenBasic::generateBiomes()
 					noise_filler_depth->result[index], 0.0f);
 				depth_water_top = biome->depth_water_top;
 				depth_riverbed = biome->depth_riverbed;
-				biome_y_min = biome->min_pos.Y;
 			}
 
 			if (c == c_stone) {
@@ -833,7 +847,7 @@ void MapgenBasic::generateCavesNoiseIntersection(s16 max_stone_y)
 	if (node_min.Y > max_stone_y || cave_width >= 10.0f)
 		return;
 
-	CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize,
+	CavesNoiseIntersection caves_noise(ndef, m_bmgr, biomegen, csize,
 		&np_cave1, &np_cave2, seed, cave_width);
 
 	caves_noise.generateCaves(vm, node_min, node_max, biomemap);
diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp
index 8b4c96cd5..5a4501693 100644
--- a/src/mapgen/mg_biome.cpp
+++ b/src/mapgen/mg_biome.cpp
@@ -147,18 +147,50 @@ BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr,
 	// fallback biome when biome generation (which calculates the biomemap IDs)
 	// is disabled.
 	memset(biomemap, 0, sizeof(biome_t) * m_csize.X * m_csize.Z);
+
+	// Calculating the bounding position of each biome so we know when we might switch
+	// First gathering all heights where we might switch
+	std::vector<s16> temp_transition_heights;
+	temp_transition_heights.reserve(m_bmgr->getNumObjects() * 2);
+	for (size_t i = 0; i < m_bmgr->getNumObjects(); i++) {
+		Biome *b = (Biome *)m_bmgr->getRaw(i);
+		temp_transition_heights.push_back(b->max_pos.Y);
+		temp_transition_heights.push_back(b->min_pos.Y);
+	}
+
+	// Sorting the biome transition points
+	std::sort(temp_transition_heights.begin(), temp_transition_heights.end(), std::greater<int>());
+
+	// Getting rid of duplicate biome transition points
+	s16 last = temp_transition_heights[0];
+	size_t out_pos = 1;
+	for (size_t i = 1; i < temp_transition_heights.size(); i++){
+		if (temp_transition_heights[i] != last) {
+			last = temp_transition_heights[i];
+			temp_transition_heights[out_pos++] = last;
+		}
+	}
+
+	biome_transitions = new s16[out_pos];
+	memcpy(biome_transitions, temp_transition_heights.data(), sizeof(s16) * out_pos);
 }
 
 BiomeGenOriginal::~BiomeGenOriginal()
 {
 	delete []biomemap;
 
+	delete []biome_transitions;
 	delete noise_heat;
 	delete noise_humidity;
 	delete noise_heat_blend;
 	delete noise_humidity_blend;
 }
 
+s16* BiomeGenOriginal::getBiomeTransitions() const
+{
+	return biome_transitions;
+}
+
 BiomeGen *BiomeGenOriginal::clone(BiomeManager *biomemgr) const
 {
 	return new BiomeGenOriginal(biomemgr, m_params, m_csize);
diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h
index c85afc3a0..567a0fe81 100644
--- a/src/mapgen/mg_biome.h
+++ b/src/mapgen/mg_biome.h
@@ -128,8 +128,11 @@ public:
 	// Same as above, but uses a raw numeric index correlating to the (x,z) position.
 	virtual Biome *getBiomeAtIndex(size_t index, v3s16 pos) const = 0;
 
+	virtual s16 *getBiomeTransitions() const = 0;
+
 	// Result of calcBiomes bulk computation.
 	biome_t *biomemap = nullptr;
+	s16 *biome_transitions = nullptr;
 
 protected:
 	BiomeManager *m_bmgr = nullptr;
@@ -186,6 +189,7 @@ public:
 	Biome *getBiomeAtIndex(size_t index, v3s16 pos) const;
 
 	Biome *calcBiomeFromNoise(float heat, float humidity, v3s16 pos) const;
+	s16 *getBiomeTransitions() const;
 
 	float *heatmap;
 	float *humidmap;