diff --git a/games/minimal/mods/default/mapgen.lua b/games/minimal/mods/default/mapgen.lua
index af94505a04f5856e8e26944b2d1155a8ec79a493..115bb145879e8d70004604d7edfeb9f3df30372f 100644
--- a/games/minimal/mods/default/mapgen.lua
+++ b/games/minimal/mods/default/mapgen.lua
@@ -27,7 +27,7 @@ minetest.register_alias("mapgen_mese", "default:mese")
 -- Ore generation
 --
 
-local function generate_ore(name, wherein, minp, maxp, seed, chunks_per_volume, ore_per_chunk, height_min, height_max)
+local function generate_ore(name, wherein, minp, maxp, seed, chunks_per_volume, ore_per_chunk, height_min, height_max, param2)
 	if maxp.y < height_min or minp.y > height_max then
 		return
 	end
@@ -57,7 +57,7 @@ local function generate_ore(name, wherein, minp, maxp, seed, chunks_per_volume,
 					local z2 = z0+z1
 					local p2 = {x=x2, y=y2, z=z2}
 					if minetest.env:get_node(p2).name == wherein then
-						minetest.env:set_node(p2, {name=name})
+						minetest.env:set_node(p2, {name=name, param2=param2})
 					end
 				end
 			end
@@ -110,5 +110,11 @@ minetest.register_on_generated(function(minp, maxp, seed)
 		end
 		end
 	end
+	if minetest.setting_get("liquid_finite") then
+		generate_ore("default:water_source", "default:stone", minp, maxp, seed+42, 1/24/24/24, 4,  -100,   -10,  128)
+		generate_ore("default:water_source", "default:stone", minp, maxp, seed+42, 1/28/28/28, 3,  -10000, -101, 128)
+		generate_ore("default:lava_source",  "default:stone", minp, maxp, seed+43, 1/38/38/38, 2,  -500,   -100, 128)
+		generate_ore("default:lava_source",  "default:stone", minp, maxp, seed+43, 1/30/30/30, 4,  -31000, -501, 128)
+	end
 end)
 
diff --git a/minetest.conf.example b/minetest.conf.example
index c36fe05ff92be7b3be573178c5dea92c7db233bc..8e95d7e6018049b1fe7805eec536df020677457b 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -90,6 +90,14 @@
 #enable_fog = true
 # Enable a bit lower water surface; disable for speed (not quite optimized)
 #new_style_water = false
+# Constant volume liquids
+#liquid_finite = false
+# Update liquids every .. recommend for finite: 0.2
+#liquid_update = 1.0
+# When finite liquid: relax flowing blocks to source if level near max and N nearby source blocks, more realistic, but not true constant. values: 0,1,2,3,4 : 0 - disable, 1 - most aggresive
+#liquid_relax = 1
+# optimization: faster cave flood (and not true constant)
+#liquid_fast_flood = 1
 # Enable nice leaves; disable for speed
 #new_style_leaves = true
 # Enable smooth lighting with simple ambient occlusion;
diff --git a/src/content_abm.cpp b/src/content_abm.cpp
index cbfdd2e9f8a1aa3b74a4db189286d790bad7765d..03fc82ed4d8396f4710b84c1c6e489e9f90eb65f 100644
--- a/src/content_abm.cpp
+++ b/src/content_abm.cpp
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "settings.h"
 #include "mapblock.h" // For getNodeBlockPos
 #include "treegen.h" // For treegen::make_tree
+#include "main.h" // for g_settings
 #include "map.h"
 
 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
@@ -146,11 +147,42 @@ class MakeTreesFromSaplingsABM : public ActiveBlockModifier
 	}
 };
 
+class LiquidFlowABM : public ActiveBlockModifier
+{
+private:
+	std::set<std::string> contents;
+
+public:
+	LiquidFlowABM(ServerEnvironment *env, INodeDefManager *nodemgr) 
+	{
+		std::set<content_t> liquids;
+		nodemgr->getIds("group:liquid", liquids);
+		for(std::set<content_t>::const_iterator k = liquids.begin(); k != liquids.end(); k++)
+			contents.insert(nodemgr->get(*k).liquid_alternative_flowing);
+		
+	}
+	virtual std::set<std::string> getTriggerContents()
+	{
+		return contents;
+	}
+	virtual float getTriggerInterval()
+	{ return 10.0; }
+	virtual u32 getTriggerChance()
+	{ return 10; }
+	virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n)
+	{
+		ServerMap *map = &env->getServerMap();
+		if (map->transforming_liquid_size() < 500)
+			map->transforming_liquid_add(p);
+		//if ((*map).m_transforming_liquid.size() < 500) (*map).m_transforming_liquid.push_back(p);
+	}
+};
+
 void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef)
 {
 	env->addActiveBlockModifier(new GrowGrassABM());
 	env->addActiveBlockModifier(new RemoveGrassABM());
 	env->addActiveBlockModifier(new MakeTreesFromSaplingsABM());
+	if (g_settings->getBool("liquid_finite"))
+		env->addActiveBlockModifier(new LiquidFlowABM(env, nodedef));
 }
-
-
diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp
index 4a5c001d5201aa889fb8c9ed0c94661144940276..0d80dc17339d59073f8b3c7702a6b9e037f2ff98 100644
--- a/src/content_mapblock.cpp
+++ b/src/content_mapblock.cpp
@@ -261,7 +261,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 						level = (-0.5+node_liquid_level) * BS;
 					else if(n2.getContent() == c_flowing)
 						level = (-0.5 + ((float)(n2.param2&LIQUID_LEVEL_MASK)
-								+ 0.5) / 8.0 * node_liquid_level) * BS;
+								+ 0.5) / (float)LIQUID_LEVEL_SOURCE * node_liquid_level) * BS;
 
 					// Check node above neighbor.
 					// NOTE: This doesn't get executed if neighbor
@@ -324,7 +324,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
 					}
 				}
 				if(air_count >= 2)
-					cornerlevel = -0.5*BS;
+					cornerlevel = -0.5*BS+0.1;
 				else if(valid_count > 0)
 					cornerlevel /= valid_count;
 				corner_levels[i] = cornerlevel;
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 4307f761020996b106457a75b319475725a7de7c..7afe8df051bf8ed2df8780422ceffdd60b96cae3 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -206,6 +206,11 @@ void set_default_settings(Settings *settings)
 	//mapgen related things
 	settings->setDefault("mg_name", "v6");
 	settings->setDefault("water_level", "1");
+	settings->setDefault("liquid_finite", "false");
+	settings->setDefault("liquid_update", "1.0");
+	settings->setDefault("liquid_relax", "1");
+	settings->setDefault("liquid_fast_flood", "1");
+	
 	settings->setDefault("chunksize", "5");
 	settings->setDefault("mg_flags", "trees, caves, v6_biome_blend");
 	settings->setDefault("mgv6_freq_desert", "0.45");
diff --git a/src/map.cpp b/src/map.cpp
index 74bea3dac7272f81f672ec6243e2d093a029562a..07af2676b45a104c617784f74ca689e35fc29dc0 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -1614,10 +1614,322 @@ struct NodeNeighbor {
 	MapNode n;
 	NeighborType t;
 	v3s16 p;
+	bool l; //can liquid
+	bool i; //infinity
 };
 
+void Map::transforming_liquid_add(v3s16 p) {
+        m_transforming_liquid.push_back(p);
+}
+
+s32 Map::transforming_liquid_size() {
+        return m_transforming_liquid.size();
+}
+
+const v3s16 g_7dirs[7] =
+{
+	// +right, +top, +back
+	v3s16( 0,-1, 0), // bottom
+	v3s16( 0, 0, 0), // self
+	v3s16( 0, 0, 1), // back
+	v3s16( 0, 0,-1), // front
+	v3s16( 1, 0, 0), // right
+	v3s16(-1, 0, 0), // left
+	v3s16( 0, 1, 0)  // top
+};
+
+#define D_BOTTOM 0
+#define D_TOP 6
+#define D_SELF 1
+
+void Map::transformLiquidsFinite(core::map<v3s16, MapBlock*> & modified_blocks)
+{
+	INodeDefManager *nodemgr = m_gamedef->ndef();
+
+	DSTACK(__FUNCTION_NAME);
+	//TimeTaker timer("transformLiquids()");
+
+	u32 loopcount = 0;
+	u32 initial_size = m_transforming_liquid.size();
+
+	u8 relax = g_settings->getS16("liquid_relax");
+	bool fast_flood = g_settings->getS16("liquid_fast_flood");
+	int water_level = g_settings->getS16("water_level");
+
+	/*if(initial_size != 0)
+		infostream<<"transformLiquids(): initial_size="<<initial_size<<std::endl;*/
+
+	// list of nodes that due to viscosity have not reached their max level height
+	UniqueQueue<v3s16> must_reflow, must_reflow_second;
+
+	// List of MapBlocks that will require a lighting update (due to lava)
+	core::map<v3s16, MapBlock*> lighting_modified_blocks;
+
+	while(m_transforming_liquid.size() > 0)
+	{
+		// This should be done here so that it is done when continue is used
+		if(loopcount >= initial_size || loopcount >= 1000)
+			break;
+		loopcount++;
+		/*
+			Get a queued transforming liquid node
+		*/
+		v3s16 p0 = m_transforming_liquid.pop_front();
+		u16 total_level = 0;
+		NodeNeighbor neighbors[7]; // surrounding flowing liquid nodes
+		s8 liquid_levels[7]      = {-1, -1, -1, -1, -1, -1, -1}; // current level of every block
+		s8 liquid_levels_want[7] = {-1, -1, -1, -1, -1, -1, -1}; // target levels
+		s8 can_liquid_same_level = 0;
+		content_t liquid_kind = CONTENT_IGNORE;
+		content_t liquid_kind_flowing = CONTENT_IGNORE;
+		/*
+			Collect information about the environment
+		 */
+		const v3s16 *dirs = g_7dirs;
+		for (u16 i = 0; i < 7; i++) {
+			NeighborType nt = NEIGHBOR_SAME_LEVEL;
+			switch (i) {
+				case D_TOP:
+					nt = NEIGHBOR_UPPER;
+					break;
+				case D_BOTTOM:
+					nt = NEIGHBOR_LOWER;
+					break;
+			}
+			v3s16 npos = p0 + dirs[i];
+
+			neighbors[i].n = getNodeNoEx(npos);
+			neighbors[i].t = nt;
+			neighbors[i].p = npos;
+			neighbors[i].l = 0;
+			neighbors[i].i = 0;
+			NodeNeighbor & nb = neighbors[i];
+
+			switch (nodemgr->get(nb.n.getContent()).liquid_type) {
+				case LIQUID_NONE:
+					if (nb.n.getContent() == CONTENT_AIR) {
+						liquid_levels[i] = 0;
+						nb.l = 1;
+					}
+					break;
+				case LIQUID_SOURCE:
+					// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
+					if (liquid_kind_flowing == CONTENT_IGNORE)
+						liquid_kind_flowing = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing);
+					if (liquid_kind == CONTENT_IGNORE)
+						liquid_kind = nb.n.getContent();
+					if (nb.n.getContent() == liquid_kind) {
+						liquid_levels[i] = LIQUID_LEVEL_SOURCE;
+						nb.l = 1;
+						nb.i = (nb.n.param2 & LIQUID_INFINITY_MASK);
+					}
+					break;
+				case LIQUID_FLOWING:
+					// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
+					if (liquid_kind_flowing == CONTENT_IGNORE)
+						liquid_kind_flowing = nb.n.getContent();
+					if (liquid_kind == CONTENT_IGNORE)
+						liquid_kind = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_source);
+					if (nb.n.getContent() == liquid_kind_flowing) {
+						liquid_levels[i] = (nb.n.param2 & LIQUID_LEVEL_MASK);
+						nb.l = 1;
+					}
+					break;
+			}
+			if (nb.l && nb.t == NEIGHBOR_SAME_LEVEL) ++can_liquid_same_level;
+			if (liquid_levels[i] > 0) total_level += liquid_levels[i];
+
+			/*
+			infostream << "get node i=" <<(int)i<<" " << PP(npos) << " c="<<nb.n.getContent() <<" p0="<< (int)nb.n.param0 <<" p1="<< (int)nb.n.param1 <<" p2="<< (int)nb.n.param2 << " lt="<<nodemgr->get(nb.n.getContent()).liquid_type
+			//<< " lk=" << liquid_kind << " lkf=" << liquid_kind_flowing
+			<< " l="<< nb.l	<< " inf="<< nb.i << " nlevel=" <<  (int)liquid_levels[i] << " tlevel=" << (int)total_level << " cansame="<<(int)can_liquid_same_level<<std::endl;
+			*/
+		}
+
+		if (liquid_kind == CONTENT_IGNORE || !neighbors[D_SELF].l || total_level <= 0)
+			continue;
+
+		// fill bottom block
+		if (neighbors[D_BOTTOM].l) {
+			liquid_levels_want[D_BOTTOM] = total_level > LIQUID_LEVEL_SOURCE ? LIQUID_LEVEL_SOURCE : total_level;
+			total_level -= liquid_levels_want[D_BOTTOM];
+		}
+
+		if (relax && p0.Y <= water_level && liquid_levels[D_TOP] == 0 && total_level >= LIQUID_LEVEL_SOURCE * can_liquid_same_level - can_liquid_same_level + 2 && can_liquid_same_level >= relax + 1) { //relax up
+			total_level = LIQUID_LEVEL_SOURCE * can_liquid_same_level; 
+		}
+
+		// calculate self level 5 blocks
+		u8 want_level = 
+			  total_level >= LIQUID_LEVEL_SOURCE * can_liquid_same_level
+			? LIQUID_LEVEL_SOURCE 
+			: total_level / can_liquid_same_level;
+		total_level -= want_level * can_liquid_same_level;
+
+		if (relax && p0.Y > water_level && liquid_levels[D_TOP] == 0 && liquid_levels[D_BOTTOM] == LIQUID_LEVEL_SOURCE && want_level == 0 && total_level <= can_liquid_same_level - 2 && can_liquid_same_level >= relax + 1) { //relax down
+			total_level = 0;
+		}
+
+		for (u16 ii = D_SELF; ii < D_TOP; ++ii) { // fill only same level
+			if (!neighbors[ii].l)
+				continue;
+			liquid_levels_want[ii] = want_level;
+			if (liquid_levels_want[ii] < LIQUID_LEVEL_SOURCE && total_level > 0
+				&& liquid_levels[ii] > liquid_levels_want[ii]
+				) {
+				++liquid_levels_want[ii];
+				--total_level;
+			}
+		}
+
+		for (u16 ii = 0; ii < 7; ++ii) {
+			if (total_level < 1) break;
+			if (liquid_levels_want[ii] >= 0 && liquid_levels_want[ii] < LIQUID_LEVEL_SOURCE) {
+				++liquid_levels_want[ii];
+				--total_level;
+			}
+		}
+
+		// fill top block if can
+		if (neighbors[D_TOP].l) {
+			liquid_levels_want[D_TOP] = total_level > LIQUID_LEVEL_SOURCE ? LIQUID_LEVEL_SOURCE : total_level ;
+			total_level -= liquid_levels_want[D_TOP];
+		}
+
+		for (u16 ii = 0; ii < 7; ii++) // infinity and cave flood optimization
+			if (liquid_levels_want[ii] >= 0 &&
+				(neighbors[ii].i ||
+				 (fast_flood && p0.Y < water_level &&
+				  (initial_size >= 1000
+				   && ii != D_TOP
+				   && want_level >= LIQUID_LEVEL_SOURCE/4
+				   && can_liquid_same_level >= 5
+				   && liquid_levels[D_TOP] >= LIQUID_LEVEL_SOURCE))))
+				liquid_levels_want[ii] = LIQUID_LEVEL_SOURCE;
+
+		//if (total_level > 0 /*|| flowed != volume*/) infostream <<" AFTER level=" << (int)total_level /*<< " flowed="<<flowed<< " volume=" <<volume*/<< " wantsame="<<(int)want_level<< " top="<< (int)liquid_levels_want[D_TOP]<< " topwas="<< (int)liquid_levels[D_TOP]<< " bot="<< (int)liquid_levels_want[D_BOTTOM]<<std::endl;
+
+		u8 changed = 0;
+		for (u16 i = 0; i < 7; i++) {
+			if (liquid_levels_want[i] < 0 || !neighbors[i].l) 
+				continue;
+			MapNode & n0 = neighbors[i].n;
+			p0 = neighbors[i].p;
+			/*
+				decide on the type (and possibly level) of the current node
+			*/
+			content_t new_node_content;
+			s8 new_node_level = -1;
+			u8 viscosity = nodemgr->get(liquid_kind).liquid_viscosity;
+			if (viscosity > 1 && liquid_levels_want[i] != liquid_levels[i]) {
+				// amount to gain, limited by viscosity
+				// must be at least 1 in absolute value
+				s8 level_inc = liquid_levels_want[i] - liquid_levels[i];
+				if (level_inc < -viscosity || level_inc > viscosity)
+					new_node_level = liquid_levels[i] + level_inc/viscosity;
+				else if (level_inc < 0)
+					new_node_level = liquid_levels[i] - 1;
+				else if (level_inc > 0)
+					new_node_level = liquid_levels[i] + 1;
+			} else
+				new_node_level = liquid_levels_want[i];
+			if (new_node_level >= LIQUID_LEVEL_SOURCE)
+				new_node_content = liquid_kind;
+			else if (new_node_level > 0)
+				new_node_content = liquid_kind_flowing;
+			else
+				new_node_content = CONTENT_AIR;
+
+			// last level must flow down on stairs
+			if (liquid_levels_want[i] != liquid_levels[i] && liquid_levels[D_TOP] <= 0 && !neighbors[D_BOTTOM].l && new_node_level >= 1 && new_node_level <= 2) //maybe == 1 // 
+				for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) { // only same level
+				if (!neighbors[ii].l)
+					continue;
+				must_reflow_second.push_back(p0 + dirs[ii]);
+			}
+
+			/*
+				check if anything has changed. if not, just continue with the next node.
+			 */
+			if (
+				 new_node_content == n0.getContent() 
+				&& (nodemgr->get(n0.getContent()).liquid_type != LIQUID_FLOWING ||
+				 ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level 
+				 // &&((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK)== flowing_down
+				 ))
+				&&
+				 (nodemgr->get(n0.getContent()).liquid_type != LIQUID_SOURCE ||
+				 (((n0.param2 & LIQUID_INFINITY_MASK) == LIQUID_INFINITY_MASK) == neighbors[i].i
+				 ))
+			   ) {
+				continue;
+			}
+			++changed;
+
+			/*
+				update the current node
+			 */
+			if (nodemgr->get(new_node_content).liquid_type == LIQUID_FLOWING) {
+				// set level to last 3 bits, flowing down bit to 4th bit
+				n0.param2 = (new_node_level & LIQUID_LEVEL_MASK);
+			} else if (nodemgr->get(new_node_content).liquid_type == LIQUID_SOURCE) {
+				//n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK);
+				n0.param2 = (neighbors[i].i ? LIQUID_INFINITY_MASK : 0x00);
+			}
+			//infostream << "set node i=" <<(int)i<<" "<< PP(p0)<< " nc="<<new_node_content<< " p2="<<(int)n0.param2<< " nl="<<(int)new_node_level<<std::endl;
+			n0.setContent(new_node_content);
+			// Find out whether there is a suspect for this action
+			std::string suspect;
+			if(m_gamedef->rollback()){
+				suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1);
+			}
+
+			if(!suspect.empty()){
+				// Blame suspect
+				RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true);
+				// Get old node for rollback
+				RollbackNode rollback_oldnode(this, p0, m_gamedef);
+				// Set node
+				setNode(p0, n0);
+				// Report
+				RollbackNode rollback_newnode(this, p0, m_gamedef);
+				RollbackAction action;
+				action.setSetNode(p0, rollback_oldnode, rollback_newnode);
+				m_gamedef->rollback()->reportAction(action);
+			} else {
+				// Set node
+				setNode(p0, n0);
+			}
+
+			v3s16 blockpos = getNodeBlockPos(p0);
+			MapBlock *block = getBlockNoCreateNoEx(blockpos);
+			if(block != NULL) {
+				modified_blocks.insert(blockpos, block);
+				// If node emits light, MapBlock requires lighting update
+				if(nodemgr->get(n0).light_source != 0)
+					lighting_modified_blocks[block->getPos()] = block;
+			}
+			must_reflow.push_back(neighbors[i].p);
+		}
+		/* //for better relax
+		if (changed)  for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) { // only same level
+			if (!neighbors[ii].l) continue;
+			must_reflow.push_back(p0 + dirs[ii]);
+		}*/
+	}
+	//if (loopcount) infostream<<"Map::transformLiquids(): loopcount="<<loopcount<<" reflow="<<must_reflow.size()<<" queue="<< m_transforming_liquid.size()<<std::endl;
+	while (must_reflow.size() > 0)
+		m_transforming_liquid.push_back(must_reflow.pop_front());
+	while (must_reflow_second.size() > 0)
+		m_transforming_liquid.push_back(must_reflow_second.pop_front());
+	updateLighting(lighting_modified_blocks, modified_blocks);
+}
+
 void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 {
+
+	if (g_settings->getBool("liquid_finite")) return Map::transformLiquidsFinite(modified_blocks);
+	
 	INodeDefManager *nodemgr = m_gamedef->ndef();
 
 	DSTACK(__FUNCTION_NAME);
diff --git a/src/map.h b/src/map.h
index 1d25b636bcdac41b78770c2b88fcaa4be1409378..d356da2d1c65f00ce6fb39019a21af2d39896465 100644
--- a/src/map.h
+++ b/src/map.h
@@ -302,6 +302,7 @@ class Map /*: public NodeContainer*/
 	virtual void PrintInfo(std::ostream &out);
 
 	void transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks);
+	void transformLiquidsFinite(core::map<v3s16, MapBlock*> & modified_blocks);
 
 	/*
 		Node metadata
@@ -330,6 +331,9 @@ class Map /*: public NodeContainer*/
 		Variables
 	*/
 
+	void transforming_liquid_add(v3s16 p);
+	s32 transforming_liquid_size();
+
 protected:
 
 	std::ostream &m_dout; // A bit deprecated, could be removed
diff --git a/src/mapnode.h b/src/mapnode.h
index d70764e0eca1170c3f54111667eac94d3ed084b6..1c8c5c49c0d55596c8c313cefd84c8e2f3fd211d 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -67,10 +67,15 @@ enum LightBank
 #define LIQUID_LEVEL_MASK 0x07
 #define LIQUID_FLOW_DOWN_MASK 0x08
 
+//#define LIQUID_LEVEL_MASK 0x3f // better finite water
+//#define LIQUID_FLOW_DOWN_MASK 0x40 // not used when finite water
+
 /* maximum amount of liquid in a block */
 #define LIQUID_LEVEL_MAX LIQUID_LEVEL_MASK
 #define LIQUID_LEVEL_SOURCE (LIQUID_LEVEL_MAX+1)
 
+#define LIQUID_INFINITY_MASK 0x80 //0b10000000
+
 /*
 	This is the stuff what the whole world consists of.
 */
diff --git a/src/server.cpp b/src/server.cpp
index 5021718a39f116f653487dc42e993658a86c1049..2d00cf4aca406c57c484238b5013f9cd634a704b 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -659,6 +659,7 @@ Server::Server(
 	m_ignore_map_edit_events_peer_id(0)
 {
 	m_liquid_transform_timer = 0.0;
+	m_liquid_transform_every = 1.0;
 	m_print_info_timer = 0.0;
 	m_masterserver_timer = 0.0;
 	m_objectdata_timer = 0.0;
@@ -834,6 +835,8 @@ Server::Server(
 		Add some test ActiveBlockModifiers to environment
 	*/
 	add_legacy_abms(m_env, m_nodedef);
+
+	m_liquid_transform_every = g_settings->getFloat("liquid_update");
 }
 
 Server::~Server()
@@ -1150,9 +1153,9 @@ void Server::AsyncRunStep()
 
 	/* Transform liquids */
 	m_liquid_transform_timer += dtime;
-	if(m_liquid_transform_timer >= 1.00)
+	if(m_liquid_transform_timer >= m_liquid_transform_every)
 	{
-		m_liquid_transform_timer -= 1.00;
+		m_liquid_transform_timer -= m_liquid_transform_every;
 
 		JMutexAutoLock lock(m_env_mutex);
 
diff --git a/src/server.h b/src/server.h
index 907be485d9da6a03875860ca9b05c2a76520a978..d7700791c99ca8e2ab32a98eb25e70e689b351ad 100644
--- a/src/server.h
+++ b/src/server.h
@@ -636,6 +636,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 
 	// Some timers
 	float m_liquid_transform_timer;
+	float m_liquid_transform_every;
 	float m_print_info_timer;
 	float m_masterserver_timer;
 	float m_objectdata_timer;