]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/map.cpp
.
[dragonfireclient.git] / src / map.cpp
index c69c3f2489c0cecc5edc1e909f369f82a1f753da..1889cccf69cd2511efcafcc73dd0a362eada80f2 100644 (file)
@@ -1,30 +1,41 @@
 /*
-(c) 2010 Perttu Ahola <celeron55@gmail.com>
+Minetest-c55
+Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
 #include "map.h"
-//#include "player.h"
 #include "main.h"
 #include "jmutexautolock.h"
 #include "client.h"
 #include "filesys.h"
 #include "utility.h"
+#include "voxel.h"
+#include "porting.h"
 
-#ifdef _WIN32
-       #include <windows.h>
-       #define sleep_ms(x) Sleep(x)
-#else
-       #include <unistd.h>
-       #define sleep_ms(x) usleep(x*1000)
-#endif
+/*
+       Map
+*/
 
 Map::Map(std::ostream &dout):
        m_dout(dout),
        m_camera_position(0,0,0),
        m_camera_direction(0,0,1),
        m_sector_cache(NULL),
-       m_hwrapper(this),
-       drawoffset(0,0,0)
+       m_hwrapper(this)
 {
        m_sector_mutex.Init();
        m_camera_mutex.Init();
@@ -55,13 +66,6 @@ Map::~Map()
        }
 }
 
-/*bool Map::sectorExists(v2s16 p)
-{
-       JMutexAutoLock lock(m_sector_mutex);
-       core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
-       return (n != NULL);
-}*/
-
 MapSector * Map::getSectorNoGenerate(v2s16 p)
 {
        JMutexAutoLock lock(m_sector_mutex);
@@ -103,19 +107,20 @@ MapBlock * Map::getBlockNoCreate(v3s16 p3d)
        return block;
 }
 
-/*MapBlock * Map::getBlock(v3s16 p3d, bool generate)
+MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
 {
-       dstream<<"Map::getBlock() with generate=true called"
-                       <<std::endl;
-       v2s16 p2d(p3d.X, p3d.Z);
-       //MapSector * sector = getSector(p2d, generate);
-       MapSector * sector = getSectorNoGenerate(p2d);
-
-       if(sector == NULL)
-               throw InvalidPositionException();
-
-       return sector->getBlockNoCreate(p3d.Y);
-}*/
+       try
+       {
+               v2s16 p2d(p3d.X, p3d.Z);
+               MapSector * sector = getSectorNoGenerate(p2d);
+               MapBlock *block = sector->getBlockNoCreate(p3d.Y);
+               return block;
+       }
+       catch(InvalidPositionException &e)
+       {
+               return NULL;
+       }
+}
 
 f32 Map::getGroundHeight(v2s16 p, bool generate)
 {
@@ -158,120 +163,6 @@ bool Map::isNodeUnderground(v3s16 p)
        }
 }
 
-#ifdef LKJnb
-//TODO: Remove: Not used.
-/*
-       Goes recursively through the neighbours of the node.
-
-       Alters only transparent nodes.
-
-       If the lighting of the neighbour is lower than the lighting of
-       the node was (before changing it to 0 at the step before), the
-       lighting of the neighbour is set to 0 and then the same stuff
-       repeats for the neighbour.
-
-       Some things are made strangely to make it as fast as possible.
-
-       Usage: (for clearing all possible spreaded light of a lamp)
-       NOTE: This is outdated
-               core::list<v3s16> light_sources;
-               core::map<v3s16, MapBlock*> modified_blocks;
-               u8 oldlight = node_at_pos.light;
-               node_at_pos.setLight(0);
-               unLightNeighbors(pos, oldlight, light_sources, modified_blocks);
-*/
-void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
-               core::map<v3s16, bool> & light_sources,
-               core::map<v3s16, MapBlock*>  & modified_blocks)
-{
-       v3s16 dirs[6] = {
-               v3s16(0,0,1), // back
-               v3s16(0,1,0), // top
-               v3s16(1,0,0), // right
-               v3s16(0,0,-1), // front
-               v3s16(0,-1,0), // bottom
-               v3s16(-1,0,0), // left
-       };
-       
-       /*
-               Initialize block cache
-       */
-       v3s16 blockpos_last;
-       MapBlock *block = NULL;
-       // Cache this a bit, too
-       bool block_checked_in_modified = false;
-       
-       // Loop through 6 neighbors
-       for(u16 i=0; i<6; i++){
-               // Get the position of the neighbor node
-               v3s16 n2pos = pos + dirs[i];
-               
-               // Get the block where the node is located
-               v3s16 blockpos = getNodeBlockPos(n2pos);
-
-               // Only fetch a new block if the block position has changed
-               try{
-                       if(block == NULL || blockpos != blockpos_last)
-                       {
-                               block = getBlockNoCreate(blockpos);
-                               blockpos_last = blockpos;
-                               
-                               block_checked_in_modified = false;
-                               //blockchangecount++;
-                       }
-               }
-               catch(InvalidPositionException &e)
-               {
-                       continue;
-               }
-
-               if(block->isDummy())
-                       continue;
-               
-               // Calculate relative position in block
-               v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
-               // Get node straight from the block
-               MapNode n2 = block->getNode(relpos);
-               
-               /*
-                       If the neighbor is dimmer than what was specified
-                       as oldlight (the light of the previous node)
-               */
-               if(n2.getLight() < oldlight)
-               {
-                       /*
-                               And the neighbor is transparent and it has some light
-                       */
-                       if(n2.light_propagates() && n2.getLight() != 0)
-                       {
-                               /*
-                                       Set light to 0 and recurse.
-                               */
-                               u8 current_light = n2.getLight();
-                               n2.setLight(0);
-                               block->setNode(relpos, n2);
-                               unLightNeighbors(n2pos, current_light,
-                                               light_sources, modified_blocks);
-                               
-                               if(block_checked_in_modified == false)
-                               {
-                                       // If the block is not found in modified_blocks, add.
-                                       if(modified_blocks.find(blockpos) == NULL)
-                                       {
-                                               modified_blocks.insert(blockpos, block);
-                                       }
-                                       block_checked_in_modified = true;
-                               }
-                       }
-               }
-               else{
-                       //light_sources.push_back(n2pos);
-                       light_sources.insert(n2pos, true);
-               }
-       }
-}
-#endif
-
 /*
        Goes recursively through the neighbours of the node.
 
@@ -289,7 +180,8 @@ void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
 
        values of from_nodes are lighting values.
 */
-void Map::unspreadLight(core::map<v3s16, u8> & from_nodes,
+void Map::unspreadLight(enum LightBank bank,
+               core::map<v3s16, u8> & from_nodes,
                core::map<v3s16, bool> & light_sources,
                core::map<v3s16, MapBlock*>  & modified_blocks)
 {
@@ -389,19 +281,19 @@ void Map::unspreadLight(core::map<v3s16, u8> & from_nodes,
                                        If the neighbor is dimmer than what was specified
                                        as oldlight (the light of the previous node)
                                */
-                               if(n2.getLight() < oldlight)
+                               if(n2.getLight(bank) < oldlight)
                                {
                                        /*
                                                And the neighbor is transparent and it has some light
                                        */
-                                       if(n2.light_propagates() && n2.getLight() != 0)
+                                       if(n2.light_propagates() && n2.getLight(bank) != 0)
                                        {
                                                /*
                                                        Set light to 0 and add to queue
                                                */
 
-                                               u8 current_light = n2.getLight();
-                                               n2.setLight(0);
+                                               u8 current_light = n2.getLight(bank);
+                                               n2.setLight(bank, 0);
                                                block->setNode(relpos, n2);
 
                                                unlighted_nodes.insert(n2pos, current_light);
@@ -417,6 +309,10 @@ void Map::unspreadLight(core::map<v3s16, u8> & from_nodes,
                                                        light_sources.remove(n2pos);
                                                }*/
                                        }
+                                       
+                                       /*// DEBUG
+                                       if(light_sources.find(n2pos) != NULL)
+                                               light_sources.remove(n2pos);*/
                                }
                                else{
                                        light_sources.insert(n2pos, true);
@@ -446,27 +342,29 @@ void Map::unspreadLight(core::map<v3s16, u8> & from_nodes,
                        <<std::endl;*/
        
        if(unlighted_nodes.size() > 0)
-               unspreadLight(unlighted_nodes, light_sources, modified_blocks);
+               unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
 }
 
 /*
        A single-node wrapper of the above
 */
-void Map::unLightNeighbors(v3s16 pos, u8 lightwas,
+void Map::unLightNeighbors(enum LightBank bank,
+               v3s16 pos, u8 lightwas,
                core::map<v3s16, bool> & light_sources,
                core::map<v3s16, MapBlock*>  & modified_blocks)
 {
        core::map<v3s16, u8> from_nodes;
        from_nodes.insert(pos, lightwas);
 
-       unspreadLight(from_nodes, light_sources, modified_blocks);
+       unspreadLight(bank, from_nodes, light_sources, modified_blocks);
 }
 
 /*
        Lights neighbors of from_nodes, collects all them and then
        goes on recursively.
 */
-void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
+void Map::spreadLight(enum LightBank bank,
+               core::map<v3s16, bool> & from_nodes,
                core::map<v3s16, MapBlock*> & modified_blocks)
 {
        const v3s16 dirs[6] = {
@@ -527,7 +425,7 @@ void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
                // Get node straight from the block
                MapNode n = block->getNode(relpos);
 
-               u8 oldlight = n.getLight();
+               u8 oldlight = n.getLight(bank);
                u8 newlight = diminish_light(oldlight);
 
                // Loop through 6 neighbors
@@ -565,7 +463,7 @@ void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
                                        If the neighbor is brighter than the current node,
                                        add to list (it will light up this node on its turn)
                                */
-                               if(n2.getLight() > undiminish_light(oldlight))
+                               if(n2.getLight(bank) > undiminish_light(oldlight))
                                {
                                        lighted_nodes.insert(n2pos, true);
                                        //lighted_nodes.push_back(n2pos);
@@ -575,11 +473,11 @@ void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
                                        If the neighbor is dimmer than how much light this node
                                        would spread on it, add to list
                                */
-                               if(n2.getLight() < newlight)
+                               if(n2.getLight(bank) < newlight)
                                {
                                        if(n2.light_propagates())
                                        {
-                                               n2.setLight(newlight);
+                                               n2.setLight(bank, newlight);
                                                block->setNode(relpos, n2);
                                                lighted_nodes.insert(n2pos, true);
                                                //lighted_nodes.push_back(n2pos);
@@ -611,21 +509,22 @@ void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
                        <<std::endl;*/
        
        if(lighted_nodes.size() > 0)
-               spreadLight(lighted_nodes, modified_blocks);
+               spreadLight(bank, lighted_nodes, modified_blocks);
 }
 
 /*
        A single-node source variation of the above.
 */
-void Map::lightNeighbors(v3s16 pos,
+void Map::lightNeighbors(enum LightBank bank,
+               v3s16 pos,
                core::map<v3s16, MapBlock*> & modified_blocks)
 {
        core::map<v3s16, bool> from_nodes;
        from_nodes.insert(pos, true);
-       spreadLight(from_nodes, modified_blocks);
+       spreadLight(bank, from_nodes, modified_blocks);
 }
 
-v3s16 Map::getBrightestNeighbour(v3s16 p)
+v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
 {
        v3s16 dirs[6] = {
                v3s16(0,0,1), // back
@@ -652,8 +551,8 @@ v3s16 Map::getBrightestNeighbour(v3s16 p)
                {
                        continue;
                }
-               if(n2.getLight() > brightest_light || found_something == false){
-                       brightest_light = n2.getLight();
+               if(n2.getLight(bank) > brightest_light || found_something == false){
+                       brightest_light = n2.getLight(bank);
                        brightest_pos = n2pos;
                        found_something = true;
                }
@@ -670,6 +569,8 @@ v3s16 Map::getBrightestNeighbour(v3s16 p)
        Starting point gets sunlight.
 
        Returns the lowest y value of where the sunlight went.
+
+       Mud is turned into grass in where the sunlight stops.
 */
 s16 Map::propagateSunlight(v3s16 start,
                core::map<v3s16, MapBlock*> & modified_blocks)
@@ -694,19 +595,30 @@ s16 Map::propagateSunlight(v3s16 start,
 
                if(n.sunlight_propagates())
                {
-                       n.setLight(LIGHT_SUN);
+                       n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
                        block->setNode(relpos, n);
 
                        modified_blocks.insert(blockpos, block);
                }
-               else{
+               else
+               {
+                       // Turn mud into grass
+                       if(n.d == CONTENT_MUD)
+                       {
+                               n.d = CONTENT_GRASS;
+                               block->setNode(relpos, n);
+                               modified_blocks.insert(blockpos, block);
+                       }
+
+                       // Sunlight goes no further
                        break;
                }
        }
        return y + 1;
 }
 
-void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
+void Map::updateLighting(enum LightBank bank,
+               core::map<v3s16, MapBlock*> & a_blocks,
                core::map<v3s16, MapBlock*> & modified_blocks)
 {
        /*m_dout<<DTIME<<"Map::updateLighting(): "
@@ -716,11 +628,6 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
        bool debug=false;
        u32 count_was = modified_blocks.size();
 
-       /*core::list<MapBlock *>::Iterator i = a_blocks.begin();
-       for(; i != a_blocks.end(); i++)
-       {
-               MapBlock *block = *i;*/
-
        core::map<v3s16, bool> light_sources;
        
        core::map<v3s16, u8> unlight_from;
@@ -751,14 +658,14 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
                                try{
                                        v3s16 p(x,y,z);
                                        MapNode n = block->getNode(v3s16(x,y,z));
-                                       u8 oldlight = n.getLight();
-                                       n.setLight(0);
+                                       u8 oldlight = n.getLight(bank);
+                                       n.setLight(bank, 0);
                                        block->setNode(v3s16(x,y,z), n);
                                        
                                        // Collect borders for unlighting
                                        if(x==0 || x == MAP_BLOCKSIZE-1
-                                                       || y==0 || y == MAP_BLOCKSIZE-1
-                                                       || z==0 || z == MAP_BLOCKSIZE-1)
+                                       || y==0 || y == MAP_BLOCKSIZE-1
+                                       || z==0 || z == MAP_BLOCKSIZE-1)
                                        {
                                                v3s16 p_map = p + v3s16(
                                                                MAP_BLOCKSIZE*pos.X,
@@ -779,11 +686,22 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
                                }
                        }
                        
-                       bool bottom_valid = block->propagateSunlight(light_sources);
+                       if(bank == LIGHTBANK_DAY)
+                       {
+                               bool bottom_valid = block->propagateSunlight(light_sources);
 
-                       // If bottom is valid, we're done.
-                       if(bottom_valid)
+                               // If bottom is valid, we're done.
+                               if(bottom_valid)
+                                       break;
+                       }
+                       else if(bank == LIGHTBANK_NIGHT)
+                       {
                                break;
+                       }
+                       else
+                       {
+                               assert(0);
+                       }
                                
                        /*dstream<<"Bottom for sunlight-propagated block ("
                                        <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
@@ -804,8 +722,8 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
        }
        
        {
-               //TimeTaker timer("unspreadLight", g_device);
-               unspreadLight(unlight_from, light_sources, modified_blocks);
+               //TimeTaker timer("unspreadLight");
+               unspreadLight(bank, unlight_from, light_sources, modified_blocks);
        }
        
        if(debug)
@@ -819,10 +737,12 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
        // Yes, add it to light_sources... somehow.
        // It has to be added at somewhere above, in the loop.
        // TODO
+       // NOTE: This actually works fine without doing so
+       //       - Find out why it works
 
        {
-               //TimeTaker timer("spreadLight", g_device);
-               spreadLight(light_sources, modified_blocks);
+               //TimeTaker timer("spreadLight");
+               spreadLight(bank, light_sources, modified_blocks);
        }
        
        if(debug)
@@ -835,6 +755,24 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
        //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
 }
 
+void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
+               core::map<v3s16, MapBlock*> & modified_blocks)
+{
+       updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks);
+       updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks);
+       
+       /*
+               Update information about whether day and night light differ
+       */
+       for(core::map<v3s16, MapBlock*>::Iterator
+                       i = modified_blocks.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               MapBlock *block = i.getNode()->getValue();
+               block->updateDayNightDiff();
+       }
+}
+
 /*
        This is called after changing a node from transparent to opaque.
        The lighting value of the node should be left as-is after changing
@@ -849,12 +787,6 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
        m_dout<<DTIME<<"Map::nodeAddedUpdate(): p=("
                        <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
 
-       u8 lightwas = getNode(p).getLight();
-
-       //core::list<v3s16> light_sources;
-       core::map<v3s16, bool> light_sources;
-       //MapNode n = getNode(p);
-
        /*
                From this node to nodes underneath:
                If lighting is sunlight (1.0), unlight neighbours and
@@ -862,9 +794,11 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                Else discontinue.
        */
 
-       bool node_under_sunlight = true;
-       
        v3s16 toppos = p + v3s16(0,1,0);
+       v3s16 bottompos = p + v3s16(0,-1,0);
+
+       bool node_under_sunlight = true;
+       core::map<v3s16, bool> light_sources;
 
        /*
                If there is a node at top and it doesn't have sunlight,
@@ -875,36 +809,71 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
        try{
                MapNode topnode = getNode(toppos);
 
-               if(topnode.getLight() != LIGHT_SUN)
+               if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
                        node_under_sunlight = false;
        }
        catch(InvalidPositionException &e)
        {
        }
-
-       // Add the block of the added node to modified_blocks
-       v3s16 blockpos = getNodeBlockPos(p);
-       MapBlock * block = getBlockNoCreate(blockpos);
-       assert(block != NULL);
-       modified_blocks.insert(blockpos, block);
        
-       if(isValidPosition(p) == false)
-               throw;
+       if(n.d != CONTENT_TORCH)
+       {
+               /*
+                       If there is grass below, change it to mud
+               */
+               try{
+                       MapNode bottomnode = getNode(bottompos);
+                       
+                       if(bottomnode.d == CONTENT_GRASS
+                                       || bottomnode.d == CONTENT_GRASS_FOOTSTEPS)
+                       {
+                               bottomnode.d = CONTENT_MUD;
+                               setNode(bottompos, bottomnode);
+                       }
+               }
+               catch(InvalidPositionException &e)
+               {
+               }
+       }
+
+       enum LightBank banks[] =
+       {
+               LIGHTBANK_DAY,
+               LIGHTBANK_NIGHT
+       };
+       for(s32 i=0; i<2; i++)
+       {
+               enum LightBank bank = banks[i];
+
+               u8 lightwas = getNode(p).getLight(bank);
+
+               // Add the block of the added node to modified_blocks
+               v3s16 blockpos = getNodeBlockPos(p);
+               MapBlock * block = getBlockNoCreate(blockpos);
+               assert(block != NULL);
+               modified_blocks.insert(blockpos, block);
                
-       // Unlight neighbours of node.
-       // This means setting light of all consequent dimmer nodes
-       // to 0.
-       // This also collects the nodes at the border which will spread
-       // light again into this.
-       unLightNeighbors(p, lightwas, light_sources, modified_blocks);
-
-       n.setLight(0);
+               if(isValidPosition(p) == false)
+                       throw;
+                       
+               // Unlight neighbours of node.
+               // This means setting light of all consequent dimmer nodes
+               // to 0.
+               // This also collects the nodes at the border which will spread
+               // light again into this.
+               unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
+
+               n.setLight(bank, 0);
+       }
+       
        setNode(p, n);
        
        /*
                If node is under sunlight, take all sunlighted nodes under
                it and clear light from them and from where the light has
                been spread.
+               TODO: This could be optimized by mass-unlighting instead
+                     of looping
        */
        if(node_under_sunlight)
        {
@@ -922,11 +891,13 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                                break;
                        }
 
-                       if(n2.getLight() == LIGHT_SUN)
+                       if(n2.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
                        {
                                //m_dout<<DTIME<<"doing"<<std::endl;
-                               unLightNeighbors(n2pos, n2.getLight(), light_sources, modified_blocks);
-                               n2.setLight(0);
+                               unLightNeighbors(LIGHTBANK_DAY,
+                                               n2pos, n2.getLight(LIGHTBANK_DAY),
+                                               light_sources, modified_blocks);
+                               n2.setLight(LIGHTBANK_DAY, 0);
                                setNode(n2pos, n2);
                        }
                        else
@@ -934,11 +905,58 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                }
        }
        
+       for(s32 i=0; i<2; i++)
+       {
+               enum LightBank bank = banks[i];
+               
+               /*
+                       Spread light from all nodes that might be capable of doing so
+                       TODO: Convert to spreadLight
+               */
+               spreadLight(bank, light_sources, modified_blocks);
+       }
+
+       /*
+               Update information about whether day and night light differ
+       */
+       for(core::map<v3s16, MapBlock*>::Iterator
+                       i = modified_blocks.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               MapBlock *block = i.getNode()->getValue();
+               block->updateDayNightDiff();
+       }
+
        /*
-               Spread light from all nodes that might be capable of doing so
-               TODO: Convert to spreadLight
+               Add neighboring liquid nodes and the node itself if it is
+               liquid (=water node was added) to transform queue.
        */
-       spreadLight(light_sources, modified_blocks);
+       v3s16 dirs[7] = {
+               v3s16(0,0,0), // self
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+       for(u16 i=0; i<7; i++)
+       {
+               try
+               {
+
+               v3s16 p2 = p + dirs[i];
+               
+               MapNode n2 = getNode(p2);
+               if(content_liquid(n2.d))
+               {
+                       m_transforming_liquid.push_back(p2);
+               }
+               
+               }catch(InvalidPositionException &e)
+               {
+               }
+       }
 }
 
 /*
@@ -953,6 +971,9 @@ void Map::removeNodeAndUpdate(v3s16 p,
        bool node_under_sunlight = true;
        
        v3s16 toppos = p + v3s16(0,1,0);
+
+       // Node will be replaced with this
+       u8 replace_material = CONTENT_AIR;
        
        /*
                If there is a node at top and it doesn't have sunlight,
@@ -961,33 +982,50 @@ void Map::removeNodeAndUpdate(v3s16 p,
        try{
                MapNode topnode = getNode(toppos);
 
-               if(topnode.getLight() != LIGHT_SUN)
+               if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
                        node_under_sunlight = false;
        }
        catch(InvalidPositionException &e)
        {
        }
 
-       /*
-               Unlight neighbors (in case the node is a light source)
-       */
-       //core::list<v3s16> light_sources;
        core::map<v3s16, bool> light_sources;
-       unLightNeighbors(p, getNode(p).getLight(),
-                       light_sources, modified_blocks);
+
+       enum LightBank banks[] =
+       {
+               LIGHTBANK_DAY,
+               LIGHTBANK_NIGHT
+       };
+       for(s32 i=0; i<2; i++)
+       {
+               enum LightBank bank = banks[i];
+       
+               /*
+                       Unlight neighbors (in case the node is a light source)
+               */
+               unLightNeighbors(bank, p,
+                               getNode(p).getLight(bank),
+                               light_sources, modified_blocks);
+       }
 
        /*
-               Remove the node
+               Remove the node.
+               This also clears the lighting.
        */
+
        MapNode n;
-       n.d = MATERIAL_AIR;
-       n.setLight(0);
+       n.d = replace_material;
        setNode(p, n);
        
-       /*
-               Recalculate lighting
-       */
-       spreadLight(light_sources, modified_blocks);
+       for(s32 i=0; i<2; i++)
+       {
+               enum LightBank bank = banks[i];
+       
+               /*
+                       Recalculate lighting
+               */
+               spreadLight(bank, light_sources, modified_blocks);
+       }
 
        // Add the block of the removed node to modified_blocks
        v3s16 blockpos = getNodeBlockPos(p);
@@ -1013,15 +1051,16 @@ void Map::removeNodeAndUpdate(v3s16 p,
                        /*m_dout<<DTIME<<"lighting neighbors of node ("
                                        <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
                                        <<std::endl;*/
-                       lightNeighbors(p2, modified_blocks);
+                       lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
                }
        }
        else
        {
                // Set the lighting of this node to 0
+               // TODO: Is this needed? Lighting is cleared up there already.
                try{
                        MapNode n = getNode(p);
-                       n.setLight(0);
+                       n.setLight(LIGHTBANK_DAY, 0);
                        setNode(p, n);
                }
                catch(InvalidPositionException &e)
@@ -1030,45 +1069,206 @@ void Map::removeNodeAndUpdate(v3s16 p,
                }
        }
 
-       // Get the brightest neighbour node and propagate light from it
-       v3s16 n2p = getBrightestNeighbour(p);
-       try{
-               MapNode n2 = getNode(n2p);
-               lightNeighbors(n2p, modified_blocks);
+       for(s32 i=0; i<2; i++)
+       {
+               enum LightBank bank = banks[i];
+       
+               // Get the brightest neighbour node and propagate light from it
+               v3s16 n2p = getBrightestNeighbour(bank, p);
+               try{
+                       MapNode n2 = getNode(n2p);
+                       lightNeighbors(bank, n2p, modified_blocks);
+               }
+               catch(InvalidPositionException &e)
+               {
+               }
        }
-       catch(InvalidPositionException &e)
+
+       /*
+               Update information about whether day and night light differ
+       */
+       for(core::map<v3s16, MapBlock*>::Iterator
+                       i = modified_blocks.getIterator();
+                       i.atEnd() == false; i++)
        {
+               MapBlock *block = i.getNode()->getValue();
+               block->updateDayNightDiff();
        }
-}
 
-void Map::updateMeshes(v3s16 blockpos)
-{
-       assert(mapType() == MAPTYPE_CLIENT);
+       /*
+               Add neighboring liquid nodes to transform queue.
+       */
+       v3s16 dirs[6] = {
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+       for(u16 i=0; i<6; i++)
+       {
+               try
+               {
 
+               v3s16 p2 = p + dirs[i];
+               
+               MapNode n2 = getNode(p2);
+               if(content_liquid(n2.d))
+               {
+                       m_transforming_liquid.push_back(p2);
+               }
+               
+               }catch(InvalidPositionException &e)
+               {
+               }
+       }
+}
+
+#ifndef SERVER
+void Map::expireMeshes(bool only_daynight_diffed)
+{
+       TimeTaker timer("expireMeshes()");
+
+       core::map<v2s16, MapSector*>::Iterator si;
+       si = m_sectors.getIterator();
+       for(; si.atEnd() == false; si++)
+       {
+               MapSector *sector = si.getNode()->getValue();
+
+               core::list< MapBlock * > sectorblocks;
+               sector->getBlocks(sectorblocks);
+               
+               core::list< MapBlock * >::Iterator i;
+               for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
+               {
+                       MapBlock *block = *i;
+
+                       if(only_daynight_diffed && dayNightDiffed(block->getPos()) == false)
+                       {
+                               continue;
+                       }
+                       
+                       {
+                               JMutexAutoLock lock(block->mesh_mutex);
+                               if(block->mesh != NULL)
+                               {
+                                       /*block->mesh->drop();
+                                       block->mesh = NULL;*/
+                                       block->setMeshExpired(true);
+                               }
+                       }
+               }
+       }
+}
+
+void Map::updateMeshes(v3s16 blockpos, u32 daynight_ratio)
+{
+       assert(mapType() == MAPTYPE_CLIENT);
+
+       try{
+               v3s16 p = blockpos + v3s16(0,0,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh(daynight_ratio);
+       }
+       catch(InvalidPositionException &e){}
+       // Leading edge
+       try{
+               v3s16 p = blockpos + v3s16(-1,0,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh(daynight_ratio);
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,-1,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh(daynight_ratio);
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,0,-1);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh(daynight_ratio);
+       }
+       catch(InvalidPositionException &e){}
+       /*// Trailing edge
+       try{
+               v3s16 p = blockpos + v3s16(1,0,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh(daynight_ratio);
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,1,0);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh(daynight_ratio);
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,0,1);
+               MapBlock *b = getBlockNoCreate(p);
+               b->updateMesh(daynight_ratio);
+       }
+       catch(InvalidPositionException &e){}*/
+}
+
+#endif
+
+bool Map::dayNightDiffed(v3s16 blockpos)
+{
+       try{
+               v3s16 p = blockpos + v3s16(0,0,0);
+               MapBlock *b = getBlockNoCreate(p);
+               if(b->dayNightDiffed())
+                       return true;
+       }
+       catch(InvalidPositionException &e){}
+       // Leading edges
        try{
-               v3s16 p = blockpos + v3s16(0,0,0);
-               MapBlock *b = getBlockNoCreate(p);
-               b->updateMesh();
-       }
-       catch(InvalidPositionException &e){}
-       try{
                v3s16 p = blockpos + v3s16(-1,0,0);
                MapBlock *b = getBlockNoCreate(p);
-               b->updateMesh();
+               if(b->dayNightDiffed())
+                       return true;
        }
        catch(InvalidPositionException &e){}
        try{
                v3s16 p = blockpos + v3s16(0,-1,0);
                MapBlock *b = getBlockNoCreate(p);
-               b->updateMesh();
+               if(b->dayNightDiffed())
+                       return true;
        }
        catch(InvalidPositionException &e){}
        try{
                v3s16 p = blockpos + v3s16(0,0,-1);
                MapBlock *b = getBlockNoCreate(p);
-               b->updateMesh();
+               if(b->dayNightDiffed())
+                       return true;
        }
        catch(InvalidPositionException &e){}
+       // Trailing edges
+       try{
+               v3s16 p = blockpos + v3s16(1,0,0);
+               MapBlock *b = getBlockNoCreate(p);
+               if(b->dayNightDiffed())
+                       return true;
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,1,0);
+               MapBlock *b = getBlockNoCreate(p);
+               if(b->dayNightDiffed())
+                       return true;
+       }
+       catch(InvalidPositionException &e){}
+       try{
+               v3s16 p = blockpos + v3s16(0,0,1);
+               MapBlock *b = getBlockNoCreate(p);
+               if(b->dayNightDiffed())
+                       return true;
+       }
+       catch(InvalidPositionException &e){}
+
+       return false;
 }
 
 /*
@@ -1090,6 +1290,13 @@ void Map::timerUpdate(float dtime)
 
 void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
 {
+       /*
+               Wait for caches to be removed before continuing.
+               
+               This disables the existence of caches while locked
+       */
+       //SharedPtr<JMutexAutoLock> cachelock(m_blockcachelock.waitCaches());
+
        core::list<v2s16>::Iterator j;
        for(j=list.begin(); j!=list.end(); j++)
        {
@@ -1156,14 +1363,456 @@ void Map::PrintInfo(std::ostream &out)
        out<<"Map: ";
 }
 
+#define WATER_DROP_BOOST 4
+
+void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
+{
+       DSTACK(__FUNCTION_NAME);
+       //TimeTaker timer("transformLiquids()");
+
+       u32 loopcount = 0;
+       u32 initial_size = m_transforming_liquid.size();
+       while(m_transforming_liquid.size() != 0)
+       {
+               v3s16 p0 = m_transforming_liquid.pop_front();
+
+               MapNode n0 = getNode(p0);
+               
+               // Don't deal with non-liquids
+               if(content_liquid(n0.d) == false)
+                       continue;
+
+               bool is_source = !content_flowing_liquid(n0.d);
+               
+               u8 liquid_level = 8;
+               if(is_source == false)
+                       liquid_level = n0.param2 & 0x0f;
+               
+               // Turn possible source into non-source
+               u8 nonsource_c = make_liquid_flowing(n0.d);
+
+               /*
+                       If not source, check that some node flows into this one
+                       and what is the level of liquid in this one
+               */
+               if(is_source == false)
+               {
+                       s8 new_liquid_level_max = -1;
+
+                       v3s16 dirs_from[5] = {
+                               v3s16(0,1,0), // top
+                               v3s16(0,0,1), // back
+                               v3s16(1,0,0), // right
+                               v3s16(0,0,-1), // front
+                               v3s16(-1,0,0), // left
+                       };
+                       for(u16 i=0; i<5; i++)
+                       {
+                               try
+                               {
+
+                               bool from_top = (i==0);
+
+                               v3s16 p2 = p0 + dirs_from[i];
+                               MapNode n2 = getNode(p2);
+
+                               if(content_liquid(n2.d))
+                               {
+                                       u8 n2_nonsource_c = make_liquid_flowing(n2.d);
+                                       // Check that the liquids are the same type
+                                       if(n2_nonsource_c != nonsource_c)
+                                       {
+                                               dstream<<"WARNING: Not handling: different liquids"
+                                                               " collide"<<std::endl;
+                                               continue;
+                                       }
+                                       bool n2_is_source = !content_flowing_liquid(n2.d);
+                                       s8 n2_liquid_level = 8;
+                                       if(n2_is_source == false)
+                                               n2_liquid_level = n2.param2 & 0x07;
+                                       
+                                       s8 new_liquid_level = -1;
+                                       if(from_top)
+                                       {
+                                               //new_liquid_level = 7;
+                                               if(n2_liquid_level >= 7 - WATER_DROP_BOOST)
+                                                       new_liquid_level = 7;
+                                               else
+                                                       new_liquid_level = n2_liquid_level + WATER_DROP_BOOST;
+                                       }
+                                       else if(n2_liquid_level > 0)
+                                       {
+                                               new_liquid_level = n2_liquid_level - 1;
+                                       }
+
+                                       if(new_liquid_level > new_liquid_level_max)
+                                               new_liquid_level_max = new_liquid_level;
+                               }
+
+                               }catch(InvalidPositionException &e)
+                               {
+                               }
+                       } //for
+                       
+                       /*
+                               If liquid level should be something else, update it and
+                               add all the neighboring water nodes to the transform queue.
+                       */
+                       if(new_liquid_level_max != liquid_level)
+                       {
+                               if(new_liquid_level_max == -1)
+                               {
+                                       // Remove water alltoghether
+                                       n0.d = CONTENT_AIR;
+                                       n0.param2 = 0;
+                                       setNode(p0, n0);
+                               }
+                               else
+                               {
+                                       n0.param2 = new_liquid_level_max;
+                                       setNode(p0, n0);
+                               }
+                               
+                               // Block has been modified
+                               {
+                                       v3s16 blockpos = getNodeBlockPos(p0);
+                                       MapBlock *block = getBlockNoCreateNoEx(blockpos);
+                                       if(block != NULL)
+                                               modified_blocks.insert(blockpos, block);
+                               }
+                               
+                               /*
+                                       Add neighboring non-source liquid nodes to transform queue.
+                               */
+                               v3s16 dirs[6] = {
+                                       v3s16(0,0,1), // back
+                                       v3s16(0,1,0), // top
+                                       v3s16(1,0,0), // right
+                                       v3s16(0,0,-1), // front
+                                       v3s16(0,-1,0), // bottom
+                                       v3s16(-1,0,0), // left
+                               };
+                               for(u16 i=0; i<6; i++)
+                               {
+                                       try
+                                       {
+
+                                       v3s16 p2 = p0 + dirs[i];
+                                       
+                                       MapNode n2 = getNode(p2);
+                                       if(content_flowing_liquid(n2.d))
+                                       {
+                                               m_transforming_liquid.push_back(p2);
+                                       }
+                                       
+                                       }catch(InvalidPositionException &e)
+                                       {
+                                       }
+                               }
+                       }
+               }
+               
+               // Get a new one from queue if the node has turned into non-water
+               if(content_liquid(n0.d) == false)
+                       continue;
+
+               /*
+                       Flow water from this node
+               */
+               v3s16 dirs_to[5] = {
+                       v3s16(0,-1,0), // bottom
+                       v3s16(0,0,1), // back
+                       v3s16(1,0,0), // right
+                       v3s16(0,0,-1), // front
+                       v3s16(-1,0,0), // left
+               };
+               for(u16 i=0; i<5; i++)
+               {
+                       try
+                       {
+
+                       bool to_bottom = (i == 0);
+
+                       // If liquid is at lowest possible height, it's not going
+                       // anywhere except down
+                       if(liquid_level == 0 && to_bottom == false)
+                               continue;
+                       
+                       u8 liquid_next_level = 0;
+                       // If going to bottom
+                       if(to_bottom)
+                       {
+                               //liquid_next_level = 7;
+                               if(liquid_level >= 7 - WATER_DROP_BOOST)
+                                       liquid_next_level = 7;
+                               else
+                                       liquid_next_level = liquid_level + WATER_DROP_BOOST;
+                       }
+                       else
+                               liquid_next_level = liquid_level - 1;
+
+                       bool n2_changed = false;
+                       bool flowed = false;
+                       
+                       v3s16 p2 = p0 + dirs_to[i];
+
+                       MapNode n2 = getNode(p2);
+
+                       if(content_liquid(n2.d))
+                       {
+                               u8 n2_nonsource_c = make_liquid_flowing(n2.d);
+                               // Check that the liquids are the same type
+                               if(n2_nonsource_c != nonsource_c)
+                               {
+                                       dstream<<"WARNING: Not handling: different liquids"
+                                                       " collide"<<std::endl;
+                                       continue;
+                               }
+                               bool n2_is_source = !content_flowing_liquid(n2.d);
+                               u8 n2_liquid_level = 8;
+                               if(n2_is_source == false)
+                                       n2_liquid_level = n2.param2 & 0x07;
+                               
+                               if(to_bottom)
+                               {
+                                       flowed = true;
+                               }
+
+                               if(n2_is_source)
+                               {
+                                       // Just flow into the source, nothing changes.
+                                       // n2_changed is not set because destination didn't change
+                                       flowed = true;
+                               }
+                               else
+                               {
+                                       if(liquid_next_level > liquid_level)
+                                       {
+                                               n2.param2 = liquid_next_level;
+                                               setNode(p2, n2);
+
+                                               n2_changed = true;
+                                               flowed = true;
+                                       }
+                               }
+                       }
+                       else if(n2.d == CONTENT_AIR)
+                       {
+                               n2.d = nonsource_c;
+                               n2.param2 = liquid_next_level;
+                               setNode(p2, n2);
+                               
+                               n2_changed = true;
+                               flowed = true;
+                       }
+
+                       if(n2_changed)
+                       {
+                               m_transforming_liquid.push_back(p2);
+                               
+                               v3s16 blockpos = getNodeBlockPos(p2);
+                               MapBlock *block = getBlockNoCreateNoEx(blockpos);
+                               if(block != NULL)
+                                       modified_blocks.insert(blockpos, block);
+                       }
+                       
+                       // If n2_changed to bottom, don't flow anywhere else
+                       if(to_bottom && flowed)
+                               break;
+                               
+                       }catch(InvalidPositionException &e)
+                       {
+                       }
+               }
+
+               loopcount++;
+               //if(loopcount >= 100000)
+               if(loopcount >= initial_size * 1)
+                       break;
+       }
+       //dstream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl;
+}
+
 /*
        ServerMap
 */
 
-ServerMap::ServerMap(std::string savedir, MapgenParams params):
+ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
        Map(dout_server),
        m_heightmap(NULL)
 {
+       /*
+               Experimental and debug stuff
+       */
+       
+       {
+               dstream<<"Generating map point attribute lists"<<std::endl;
+               
+               PointAttributeList *list_baseheight = m_padb.getList("hm_baseheight");
+               PointAttributeList *list_randmax = m_padb.getList("hm_randmax");
+               PointAttributeList *list_randfactor = m_padb.getList("hm_randfactor");
+               PointAttributeList *list_plants_amount = m_padb.getList("plants_amount");
+               PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
+
+               /*
+                       NOTE: BEWARE: Too big amount of these will make map generation
+                       slow. Especially those that are read by every block emerge.
+                       
+                       Fetch times:
+                       1000 points: 2-3ms
+                       5000 points: 15ms
+                       15000 points: 40ms
+               */
+               
+               for(u32 i=0; i<5000; i++)
+               {
+                       /*u32 lim = MAP_GENERATION_LIMIT;
+                       if(i < 400)
+                               lim = 2000;*/
+
+                       u32 lim = 1000 + MAP_GENERATION_LIMIT * i / 5000;
+
+                       v3s16 p(
+                               -lim + myrand()%(lim*2),
+                               0,
+                               -lim + myrand()%(lim*2)
+                       );
+                       /*float plants_amount = (float)(myrand()%1050) / 1000.0;
+                       plants_amount = pow(plants_amount, 5);
+                       list_plants_amount->addPoint(p, Attribute(plants_amount));*/
+                       
+                       float plants_amount = 0;
+                       if(myrand()%5 == 0)
+                       {
+                               plants_amount = 1.5;
+                       }
+                       else if(myrand()%4 == 0)
+                       {
+                               plants_amount = 0.5;
+                       }
+                       else if(myrand()%2 == 0)
+                       {
+                               plants_amount = 0.03;
+                       }
+                       else
+                       {
+                               plants_amount = 0.0;
+                       }
+
+
+                       list_plants_amount->addPoint(p, Attribute(plants_amount));
+               }
+
+               for(u32 i=0; i<1000; i++)
+               {
+                       /*u32 lim = MAP_GENERATION_LIMIT;
+                       if(i < 400)
+                               lim = 2000;*/
+
+                       u32 lim = 500 + MAP_GENERATION_LIMIT * i / 1000;
+
+                       v3s16 p(
+                               -lim + myrand()%(lim*2),
+                               0,
+                               -lim + myrand()%(lim*2)
+                       );
+
+                       float caves_amount = 0;
+                       if(myrand()%5 == 0)
+                       {
+                               caves_amount = 1.0;
+                       }
+                       else if(myrand()%3 == 0)
+                       {
+                               caves_amount = 0.3;
+                       }
+                       else
+                       {
+                               caves_amount = 0.05;
+                       }
+
+                       list_caves_amount->addPoint(p, Attribute(caves_amount));
+               }
+               
+               for(u32 i=0; i<5000; i++)
+               {
+                       /*u32 lim = MAP_GENERATION_LIMIT;
+                       if(i < 400)
+                               lim = 2000;*/
+
+                       u32 lim = 1000 + MAP_GENERATION_LIMIT * i / 5000;
+
+                       v3s16 p(
+                               -lim + (myrand()%(lim*2)),
+                               0,
+                               -lim + (myrand()%(lim*2))
+                       );
+                       
+                       /*s32 bh_i = (myrand()%200) - 50;
+                       float baseheight = (float)bh_i;
+                       
+                       float m = 100.;
+                       float e = 3.;
+                       float randmax = (float)(myrand()%(int)(10.*pow(m, 1./e)))/10.;
+                       randmax = pow(randmax, e);
+
+                       //float randmax = (float)(myrand()%60);
+                       float randfactor = (float)(myrand()%450) / 1000.0 + 0.4;*/
+
+                       float baseheight = 0;
+                       float randmax = 0;
+                       float randfactor = 0;
+
+                       if(myrand()%4 == 0)
+                       {
+                               baseheight = 100;
+                               randmax = 50;
+                               randfactor = 0.63;
+                       }
+                       else if(myrand()%6 == 0)
+                       {
+                               baseheight = 200;
+                               randmax = 100;
+                               randfactor = 0.66;
+                       }
+                       else if(myrand()%4 == 0)
+                       {
+                               baseheight = -3;
+                               randmax = 30;
+                               randfactor = 0.7;
+                       }
+                       else if(myrand()%3 == 0)
+                       {
+                               baseheight = 0;
+                               randmax = 30;
+                               randfactor = 0.63;
+                       }
+                       else
+                       {
+                               baseheight = -3;
+                               randmax = 20;
+                               randfactor = 0.5;
+                       }
+
+                       list_baseheight->addPoint(p, Attribute(baseheight));
+                       list_randmax->addPoint(p, Attribute(randmax));
+                       list_randfactor->addPoint(p, Attribute(randfactor));
+               }
+
+               /*list_baseheight->addPoint(v3s16(0,0,0), Attribute(5));
+               list_randmax->addPoint(v3s16(0,0,0), Attribute(20));
+               list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.6));*/
+
+               // Easy spawn point
+               /*list_baseheight->addPoint(v3s16(0,0,0), Attribute(0));
+               list_randmax->addPoint(v3s16(0,0,0), Attribute(10));
+               list_randfactor->addPoint(v3s16(0,0,0), Attribute(0.65));*/
+       }
+
+       /*
+               Try to load map; if not found, create a new one.
+       */
+
        m_savedir = savedir;
        m_map_saving_enabled = false;
        
@@ -1212,15 +1861,25 @@ ServerMap::ServerMap(std::string savedir, MapgenParams params):
        }
 
        dstream<<DTIME<<"Initializing new map."<<std::endl;
-
-       ValueGenerator *maxgen =
-                       ValueGenerator::deSerialize(params.height_randmax);
+       
+       // Create master heightmap
+       /*ValueGenerator *maxgen =
+                       ValueGenerator::deSerialize(hmp.randmax);
        ValueGenerator *factorgen =
-                       ValueGenerator::deSerialize(params.height_randfactor);
+                       ValueGenerator::deSerialize(hmp.randfactor);
        ValueGenerator *basegen =
-                       ValueGenerator::deSerialize(params.height_base);
+                       ValueGenerator::deSerialize(hmp.base);
+       m_heightmap = new UnlimitedHeightmap
+                       (hmp.blocksize, maxgen, factorgen, basegen, &m_padb);*/
+
+       /*m_heightmap = new UnlimitedHeightmap
+                       (hmp.blocksize, &m_padb);*/
+
        m_heightmap = new UnlimitedHeightmap
-                       (params.heightmap_blocksize, maxgen, factorgen, basegen);
+                       (32, &m_padb);
+       
+       // Set map parameters
+       m_params = mp;
        
        // Create zero sector
        emergeSector(v2s16(0,0));
@@ -1304,46 +1963,17 @@ MapSector * ServerMap::emergeSector(v2s16 p2d)
        s16 hm_d = MAP_BLOCKSIZE / hm_split;
 
        ServerMapSector *sector = new ServerMapSector(this, p2d, hm_split);
+       
+       // Sector position on map in nodes
+       v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
 
        /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
                        " heightmaps and objects"<<std::endl;*/
        
-       // Loop through sub-heightmaps
-       for(s16 y=0; y<hm_split; y++)
-       for(s16 x=0; x<hm_split; x++)
-       {
-               v2s16 p_in_sector = v2s16(x,y);
-               v2s16 mhm_p = p2d * hm_split + p_in_sector;
-               f32 corners[4] = {
-                       m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
-                       m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
-                       m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
-                       m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
-               };
-
-               /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
-                               <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
-                               <<std::endl;*/
-
-               FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
-                               mhm_p, hm_d);
-               sector->setHeightmap(p_in_sector, hm);
-
-               //TODO: Make these values configurable
-               hm->generateContinued(1.0, 0.2, corners);
-               //hm->generateContinued(2.0, 0.2, corners);
-
-               //hm->print();
-               
-       }
-
        /*
-               Generate objects
+               Calculate some information about local properties
        */
        
-       core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
-       sector->setObjects(objects);
-       
        v2s16 mhm_p = p2d * hm_split;
        f32 corners[4] = {
                m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
@@ -1379,25 +2009,93 @@ MapSector * ServerMap::emergeSector(v2s16 p2d)
        pitness /= 4.0;
        pitness /= MAP_BLOCKSIZE;
        //dstream<<"pitness="<<pitness<<std::endl;
+
+       /*
+               Get local attributes
+       */
        
+       float local_plants_amount = 0.0;
+       {
+               //dstream<<"emergeSector(): Reading point attribute lists"<<std::endl;
+               //TimeTaker attrtimer("emergeSector() attribute fetch");
+               
+               // Get plant amount from attributes
+               PointAttributeList *palist = m_padb.getList("plants_amount");
+               assert(palist);
+               /*local_plants_amount =
+                               palist->getNearAttr(nodepos2d).getFloat();*/
+               local_plants_amount =
+                               palist->getInterpolatedFloat(nodepos2d);
+       }
+
+       /*
+               Generate sector heightmap
+       */
+
+       // Loop through sub-heightmaps
+       for(s16 y=0; y<hm_split; y++)
+       for(s16 x=0; x<hm_split; x++)
+       {
+               v2s16 p_in_sector = v2s16(x,y);
+               v2s16 mhm_p = p2d * hm_split + p_in_sector;
+               f32 corners[4] = {
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
+                       m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
+               };
+
+               /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
+                               <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
+                               <<std::endl;*/
+
+               FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
+                               mhm_p, hm_d);
+               sector->setHeightmap(p_in_sector, hm);
+
+               //TODO: Make these values configurable
+               //hm->generateContinued(0.0, 0.0, corners);
+               //hm->generateContinued(0.25, 0.2, corners);
+               //hm->generateContinued(0.5, 0.2, corners);
+               //hm->generateContinued(1.0, 0.2, corners);
+               //hm->generateContinued(2.0, 0.2, corners);
+               //hm->generateContinued(2.0 * avgslope, 0.5, corners);
+               hm->generateContinued(avgslope * MAP_BLOCKSIZE/8, 0.5, corners);
+
+               //hm->print();
+       }
+
+       /*
+               Generate objects
+       */
+       
+       core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
+       sector->setObjects(objects);
+
+       float area = MAP_BLOCKSIZE * MAP_BLOCKSIZE;
+
        /*
                Plant some trees if there is not much slope
        */
        {
                // Avgslope is the derivative of a hill
-               float t = avgslope * avgslope;
-               float a = MAP_BLOCKSIZE * 2;
+               //float t = avgslope * avgslope;
+               float t = avgslope;
+               float a = area/16 * m_params.plants_amount * local_plants_amount;
                u32 tree_max;
-               if(t > 0.03)
-                       tree_max = a / (t/0.03);
+               //float something = 0.17*0.17;
+               float something = 0.3;
+               if(t > something)
+                       tree_max = a / (t/something);
                else
                        tree_max = a;
-               u32 count = (rand()%(tree_max+1));
+               
+               u32 count = (myrand()%(tree_max+1));
                //u32 count = tree_max;
                for(u32 i=0; i<count; i++)
                {
-                       s16 x = (rand()%(MAP_BLOCKSIZE-2))+1;
-                       s16 z = (rand()%(MAP_BLOCKSIZE-2))+1;
+                       s16 x = (myrand()%(MAP_BLOCKSIZE-2))+1;
+                       s16 z = (myrand()%(MAP_BLOCKSIZE-2))+1;
                        s16 y = sector->getGroundHeight(v2s16(x,z))+1;
                        if(y < WATER_LEVEL)
                                continue;
@@ -1405,19 +2103,22 @@ MapSector * ServerMap::emergeSector(v2s16 p2d)
                                        SECTOR_OBJECT_TREE_1);
                }
        }
+       /*
+               Plant some bushes if sector is pit-like
+       */
        {
                // Pitness usually goes at around -0.5...0.5
                u32 bush_max = 0;
-               u32 a = MAP_BLOCKSIZE * 3;
+               u32 a = area/16 * 3.0 * m_params.plants_amount * local_plants_amount;
                if(pitness > 0)
                        bush_max = (pitness*a*4);
                if(bush_max > a)
                        bush_max = a;
-               u32 count = (rand()%(bush_max+1));
+               u32 count = (myrand()%(bush_max+1));
                for(u32 i=0; i<count; i++)
                {
-                       s16 x = rand()%(MAP_BLOCKSIZE-0)+0;
-                       s16 z = rand()%(MAP_BLOCKSIZE-0)+0;
+                       s16 x = myrand()%(MAP_BLOCKSIZE-0)+0;
+                       s16 z = myrand()%(MAP_BLOCKSIZE-0)+0;
                        s16 y = sector->getGroundHeight(v2s16(x,z))+1;
                        if(y < WATER_LEVEL)
                                continue;
@@ -1425,6 +2126,23 @@ MapSector * ServerMap::emergeSector(v2s16 p2d)
                                        SECTOR_OBJECT_BUSH_1);
                }
        }
+       /*
+               Add ravine (randomly)
+       */
+       if(m_params.ravines_amount > 0.001)
+       {
+               if(myrand()%(s32)(200.0 / m_params.ravines_amount) == 0)
+               {
+                       s16 s = 6;
+                       s16 x = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
+                       s16 z = myrand()%(MAP_BLOCKSIZE-s*2-1)+s;
+                       /*s16 x = 8;
+                       s16 z = 8;*/
+                       s16 y = sector->getGroundHeight(v2s16(x,z))+1;
+                       objects->insert(v3s16(x, y, z),
+                                       SECTOR_OBJECT_RAVINE);
+               }
+       }
 
        /*
                Insert to container
@@ -1495,6 +2213,7 @@ MapBlock * ServerMap::emergeBlock(
        }
 
        //dstream<<"Not found on disk, generating."<<std::endl;
+       //TimeTaker("emergeBlock()", g_irrlicht);
 
        /*
                Do not generate over-limit
@@ -1515,6 +2234,8 @@ MapBlock * ServerMap::emergeBlock(
        /*
                If block doesn't exist, create one.
                If it exists, it is a dummy. In that case unDummify() it.
+
+               NOTE: This already sets the map as the parent of the block
        */
        if(block == NULL)
        {
@@ -1524,136 +2245,520 @@ MapBlock * ServerMap::emergeBlock(
        {
                // Remove the block so that nobody can get a half-generated one.
                sector->removeBlock(block);
-               // Allocate the block to be a proper one.
+               // Allocate the block to contain the generated data
                block->unDummify();
        }
+       
+       /*u8 water_material = CONTENT_WATER;
+       if(g_settings.getBool("endless_water"))
+               water_material = CONTENT_WATERSOURCE;*/
+       u8 water_material = CONTENT_WATERSOURCE;
+       
+       s32 lowest_ground_y = 32767;
+       s32 highest_ground_y = -32768;
+       
+       // DEBUG
+       //sector->printHeightmaps();
+
+       for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+       for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+       {
+               //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
+
+               float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
+               //assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
+               if(surface_y_f < GROUNDHEIGHT_VALID_MINVALUE)
+               {
+                       dstream<<"WARNING: Surface height not found in sector "
+                                       "for block that is being emerged"<<std::endl;
+                       surface_y_f = 0.0;
+               }
+
+               s16 surface_y = surface_y_f;
+               //avg_ground_y += surface_y;
+               if(surface_y < lowest_ground_y)
+                       lowest_ground_y = surface_y;
+               if(surface_y > highest_ground_y)
+                       highest_ground_y = surface_y;
+
+               s32 surface_depth = 0;
+               
+               float slope = sector->getSlope(v2s16(x0,z0)).getLength();
+               
+               //float min_slope = 0.45;
+               //float max_slope = 0.85;
+               float min_slope = 0.60;
+               float max_slope = 1.20;
+               float min_slope_depth = 5.0;
+               float max_slope_depth = 0;
+
+               if(slope < min_slope)
+                       surface_depth = min_slope_depth;
+               else if(slope > max_slope)
+                       surface_depth = max_slope_depth;
+               else
+                       surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
+
+               for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+               {
+                       s16 real_y = block_y * MAP_BLOCKSIZE + y0;
+                       MapNode n;
+                       /*
+                               Calculate lighting
+                               
+                               NOTE: If there are some man-made structures above the
+                               newly created block, they won't be taken into account.
+                       */
+                       if(real_y > surface_y)
+                               n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
+
+                       /*
+                               Calculate material
+                       */
+
+                       // If node is over heightmap y, it's air or water
+                       if(real_y > surface_y)
+                       {
+                               // If under water level, it's water
+                               if(real_y < WATER_LEVEL)
+                               {
+                                       n.d = water_material;
+                                       n.setLight(LIGHTBANK_DAY,
+                                                       diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
+                                       /*
+                                               Add to transforming liquid queue (in case it'd
+                                               start flowing)
+                                       */
+                                       v3s16 real_pos = v3s16(x0,y0,z0) + p*MAP_BLOCKSIZE;
+                                       m_transforming_liquid.push_back(real_pos);
+                               }
+                               // else air
+                               else
+                                       n.d = CONTENT_AIR;
+                       }
+                       // Else it's ground or dungeons (air)
+                       else
+                       {
+                               // If it's surface_depth under ground, it's stone
+                               if(real_y <= surface_y - surface_depth)
+                               {
+                                       n.d = CONTENT_STONE;
+                               }
+                               else
+                               {
+                                       // It is mud if it is under the first ground
+                                       // level or under water
+                                       if(real_y < WATER_LEVEL || real_y <= surface_y - 1)
+                                       {
+                                               n.d = CONTENT_MUD;
+                                       }
+                                       else
+                                       {
+                                               n.d = CONTENT_GRASS;
+                                       }
+
+                                       //n.d = CONTENT_MUD;
+                                       
+                                       /*// If under water level, it's mud
+                                       if(real_y < WATER_LEVEL)
+                                               n.d = CONTENT_MUD;
+                                       // Only the topmost node is grass
+                                       else if(real_y <= surface_y - 1)
+                                               n.d = CONTENT_MUD;
+                                       else
+                                               n.d = CONTENT_GRASS;*/
+                               }
+                       }
+
+                       block->setNode(v3s16(x0,y0,z0), n);
+               }
+       }
+       
+       /*
+               Calculate some helper variables
+       */
+       
+       // Completely underground if the highest part of block is under lowest
+       // ground height.
+       // This has to be very sure; it's probably one too strict now but
+       // that's just better.
+       bool completely_underground =
+                       block_y * MAP_BLOCKSIZE + MAP_BLOCKSIZE < lowest_ground_y;
+
+       bool some_part_underground = block_y * MAP_BLOCKSIZE <= highest_ground_y;
+
+       bool mostly_underwater_surface = false;
+       if(highest_ground_y < WATER_LEVEL
+                       && some_part_underground && !completely_underground)
+               mostly_underwater_surface = true;
+
+       /*
+               Get local attributes
+       */
+
+       //dstream<<"emergeBlock(): Getting local attributes"<<std::endl;
+
+       float caves_amount = 0;
+       
+       {
+               /*
+                       NOTE: BEWARE: Too big amount of attribute points slows verything
+                       down by a lot.
+                       1 interpolation from 5000 points takes 2-3ms.
+               */
+               //TimeTaker timer("emergeBlock() local attribute retrieval");
+               v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
+               PointAttributeList *list_caves_amount = m_padb.getList("caves_amount");
+               caves_amount = list_caves_amount->getInterpolatedFloat(nodepos2d);
+       }
 
-       // Randomize a bit. This makes dungeons.
-       bool low_block_is_empty = false;
-       if(rand() % 4 == 0)
-               low_block_is_empty = true;
-       
-       // This is the basic material of what the visible flat ground
-       // will consist of
-       u8 material = MATERIAL_GRASS;
-       
-       s32 lowest_ground_y = 32767;
-       
-       // DEBUG
-       //sector->printHeightmaps();
+       //dstream<<"emergeBlock(): Done"<<std::endl;
 
-       for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
-       for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+       /*
+               Generate dungeons
+       */
+
+       // Initialize temporary table
+       const s32 ued = MAP_BLOCKSIZE;
+       bool underground_emptiness[ued*ued*ued];
+       for(s32 i=0; i<ued*ued*ued; i++)
        {
-               //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
-               float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
+               underground_emptiness[i] = 0;
+       }
+       
+       // Fill table
+#if 1
+       {
+               /*
+                       Initialize orp and ors. Try to find if some neighboring
+                       MapBlock has a tunnel ended in its side
+               */
 
-               assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
+               v3f orp(
+                       (float)(myrand()%ued)+0.5,
+                       (float)(myrand()%ued)+0.5,
+                       (float)(myrand()%ued)+0.5
+               );
                
-               s16 surface_y = surface_y_f;
-               //avg_ground_y += surface_y;
-               if(surface_y < lowest_ground_y)
-                       lowest_ground_y = surface_y;
+               bool found_existing = false;
 
-               s32 surface_depth = 0;
+               // Check z-
+               try
+               {
+                       s16 z = -1;
+                       for(s16 y=0; y<ued; y++)
+                       for(s16 x=0; x<ued; x++)
+                       {
+                               v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
+                               if(getNode(ap).d == CONTENT_AIR)
+                               {
+                                       orp = v3f(x+1,y+1,0);
+                                       found_existing = true;
+                                       goto continue_generating;
+                               }
+                       }
+               }
+               catch(InvalidPositionException &e){}
                
-               float slope = sector->getSlope(v2s16(x0,z0)).getLength();
+               // Check z+
+               try
+               {
+                       s16 z = ued;
+                       for(s16 y=0; y<ued; y++)
+                       for(s16 x=0; x<ued; x++)
+                       {
+                               v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
+                               if(getNode(ap).d == CONTENT_AIR)
+                               {
+                                       orp = v3f(x+1,y+1,ued-1);
+                                       found_existing = true;
+                                       goto continue_generating;
+                               }
+                       }
+               }
+               catch(InvalidPositionException &e){}
                
-               float min_slope = 0.45;
-               float max_slope = 0.85;
-               float min_slope_depth = 5.0;
-               float max_slope_depth = 0;
-               if(slope < min_slope)
-                       surface_depth = min_slope_depth;
-               else if(slope > max_slope)
-                       surface_depth = max_slope_depth;
+               // Check x-
+               try
+               {
+                       s16 x = -1;
+                       for(s16 y=0; y<ued; y++)
+                       for(s16 z=0; z<ued; z++)
+                       {
+                               v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
+                               if(getNode(ap).d == CONTENT_AIR)
+                               {
+                                       orp = v3f(0,y+1,z+1);
+                                       found_existing = true;
+                                       goto continue_generating;
+                               }
+                       }
+               }
+               catch(InvalidPositionException &e){}
+               
+               // Check x+
+               try
+               {
+                       s16 x = ued;
+                       for(s16 y=0; y<ued; y++)
+                       for(s16 z=0; z<ued; z++)
+                       {
+                               v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
+                               if(getNode(ap).d == CONTENT_AIR)
+                               {
+                                       orp = v3f(ued-1,y+1,z+1);
+                                       found_existing = true;
+                                       goto continue_generating;
+                               }
+                       }
+               }
+               catch(InvalidPositionException &e){}
+
+               // Check y-
+               try
+               {
+                       s16 y = -1;
+                       for(s16 x=0; x<ued; x++)
+                       for(s16 z=0; z<ued; z++)
+                       {
+                               v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
+                               if(getNode(ap).d == CONTENT_AIR)
+                               {
+                                       orp = v3f(x+1,0,z+1);
+                                       found_existing = true;
+                                       goto continue_generating;
+                               }
+                       }
+               }
+               catch(InvalidPositionException &e){}
+               
+               // Check y+
+               try
+               {
+                       s16 y = ued;
+                       for(s16 x=0; x<ued; x++)
+                       for(s16 z=0; z<ued; z++)
+                       {
+                               v3s16 ap = v3s16(x,y,z) + block->getPosRelative();
+                               if(getNode(ap).d == CONTENT_AIR)
+                               {
+                                       orp = v3f(x+1,ued-1,z+1);
+                                       found_existing = true;
+                                       goto continue_generating;
+                               }
+                       }
+               }
+               catch(InvalidPositionException &e){}
+
+continue_generating:
+               
+               /*
+                       Choose whether to actually generate dungeon
+               */
+               bool do_generate_dungeons = true;
+               // Don't generate if no part is underground
+               if(!some_part_underground)
+               {
+                       do_generate_dungeons = false;
+               }
+               // Don't generate if mostly underwater surface
+               else if(mostly_underwater_surface)
+               {
+                       do_generate_dungeons = false;
+               }
+               // Partly underground = cave
+               else if(!completely_underground)
+               {
+                       do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
+               }
+               // Found existing dungeon underground
+               else if(found_existing && completely_underground)
+               {
+                       do_generate_dungeons = (rand() % 100 <= (s32)(caves_amount*100));
+               }
+               // Underground and no dungeons found
                else
-                       surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
+               {
+                       do_generate_dungeons = (rand() % 300 <= (s32)(caves_amount*100));
+               }
 
-               for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++){
-                       s16 real_y = block_y * MAP_BLOCKSIZE + y0;
-                       MapNode n;
-                       /*
-                               Calculate lighting
-                               
-                               FIXME: If there are some man-made structures above the
-                               newly created block, they won't be taken into account.
-                       */
-                       if(real_y > surface_y)
-                               n.setLight(LIGHT_SUN);
+               if(do_generate_dungeons)
+               {
                        /*
-                               Calculate material
+                               Generate some tunnel starting from orp and ors
                        */
-                       // If node is very low
-                       if(real_y <= surface_y - 10){
-                               // Create dungeons
-                               if(low_block_is_empty){
-                                       n.d = MATERIAL_AIR;
-                               }
-                               else{
-                                       n.d = MATERIAL_STONE;
+                       for(u16 i=0; i<3; i++)
+                       {
+                               v3f rp(
+                                       (float)(myrand()%ued)+0.5,
+                                       (float)(myrand()%ued)+0.5,
+                                       (float)(myrand()%ued)+0.5
+                               );
+                               s16 min_d = 0;
+                               s16 max_d = 6;
+                               s16 rs = (myrand()%(max_d-min_d+1))+min_d;
+                               
+                               v3f vec = rp - orp;
+
+                               for(float f=0; f<1.0; f+=0.04)
+                               {
+                                       v3f fp = orp + vec * f;
+                                       v3s16 cp(fp.X, fp.Y, fp.Z);
+                                       s16 d0 = -rs/2;
+                                       s16 d1 = d0 + rs - 1;
+                                       for(s16 z0=d0; z0<=d1; z0++)
+                                       {
+                                               s16 si = rs - abs(z0);
+                                               for(s16 x0=-si; x0<=si-1; x0++)
+                                               {
+                                                       s16 si2 = rs - abs(x0);
+                                                       for(s16 y0=-si2+1; y0<=si2-1; y0++)
+                                                       {
+                                                               s16 z = cp.Z + z0;
+                                                               s16 y = cp.Y + y0;
+                                                               s16 x = cp.X + x0;
+                                                               v3s16 p(x,y,z);
+                                                               if(isInArea(p, ued) == false)
+                                                                       continue;
+                                                               underground_emptiness[ued*ued*z + ued*y + x] = 1;
+                                                       }
+                                               }
+                                       }
                                }
+
+                               orp = rp;
                        }
-                       // If node is under surface level
-                       else if(real_y <= surface_y - surface_depth)
-                               n.d = MATERIAL_STONE;
-                       // If node is at or under heightmap y
-                       else if(real_y <= surface_y)
-                               n.d = material;
-                       // If node is over heightmap y
-                       else{
-                               // If under water level, it's water
-                               if(real_y < WATER_LEVEL)
+               }
+       }
+#endif
+
+       // Set to true if has caves.
+       // Set when some non-air is changed to air when making caves.
+       bool has_dungeons = false;
+
+       /*
+               Apply temporary cave data to block
+       */
+
+       for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+       for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+       {
+               for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+               {
+                       MapNode n = block->getNode(v3s16(x0,y0,z0));
+
+                       // Create dungeons
+                       if(underground_emptiness[
+                                       ued*ued*(z0*ued/MAP_BLOCKSIZE)
+                                       +ued*(y0*ued/MAP_BLOCKSIZE)
+                                       +(x0*ued/MAP_BLOCKSIZE)])
+                       {
+                               if(is_ground_content(n.d))
                                {
-                                       n.d = MATERIAL_WATER;
-                                       n.setLight(diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
+                                       // Has now caves
+                                       has_dungeons = true;
+                                       // Set air to node
+                                       n.d = CONTENT_AIR;
                                }
-                               // else air
-                               else
-                                       n.d = MATERIAL_AIR;
                        }
+
                        block->setNode(v3s16(x0,y0,z0), n);
                }
        }
+       
+       /*
+               This is used for guessing whether or not the block should
+               receive sunlight from the top if the top block doesn't exist
+       */
+       block->setIsUnderground(completely_underground);
 
        /*
-               Calculate is_underground
+               Force lighting update if some part of block is partly
+               underground and has caves.
        */
-       // Probably underground if the highest part of block is under lowest
-       // ground height
-       bool is_underground = (block_y+1) * MAP_BLOCKSIZE < lowest_ground_y;
-       block->setIsUnderground(is_underground);
+       /*if(some_part_underground && !completely_underground && has_dungeons)
+       {
+               //dstream<<"Half-ground caves"<<std::endl;
+               lighting_invalidated_blocks[block->getPos()] = block;
+       }*/
+       
+       // DEBUG: Always update lighting
+       //lighting_invalidated_blocks[block->getPos()] = block;
 
        /*
                Add some minerals
        */
 
-       if(is_underground && low_block_is_empty == false)
+       if(some_part_underground)
        {
-               s16 underground_level = lowest_ground_y/MAP_BLOCKSIZE - block_y;
-               for(s16 i=0; i<underground_level*3; i++)
+               s16 underground_level = (lowest_ground_y/MAP_BLOCKSIZE - block_y)+1;
+
+               /*
+                       Add meseblocks
+               */
+               for(s16 i=0; i<underground_level/4 + 1; i++)
+               {
+                       if(myrand()%50 == 0)
+                       {
+                               v3s16 cp(
+                                       (myrand()%(MAP_BLOCKSIZE-2))+1,
+                                       (myrand()%(MAP_BLOCKSIZE-2))+1,
+                                       (myrand()%(MAP_BLOCKSIZE-2))+1
+                               );
+
+                               MapNode n;
+                               n.d = CONTENT_MESE;
+                               
+                               //if(is_ground_content(block->getNode(cp).d))
+                               if(block->getNode(cp).d == CONTENT_STONE)
+                                       if(myrand()%8 == 0)
+                                               block->setNode(cp, n);
+
+                               for(u16 i=0; i<26; i++)
+                               {
+                                       //if(is_ground_content(block->getNode(cp+g_26dirs[i]).d))
+                                       if(block->getNode(cp+g_26dirs[i]).d == CONTENT_STONE)
+                                               if(myrand()%8 == 0)
+                                                       block->setNode(cp+g_26dirs[i], n);
+                               }
+                       }
+               }
+
+               /*
+                       Add coal
+               */
+               u16 coal_amount = 30.0 * g_settings.getFloat("coal_amount");
+               u16 coal_rareness = 60 / coal_amount;
+               if(coal_rareness == 0)
+                       coal_rareness = 1;
+               if(myrand()%coal_rareness == 0)
                {
-                       if(rand()%2 == 0)
+                       u16 a = myrand() % 16;
+                       u16 amount = coal_amount * a*a*a / 1000;
+                       for(s16 i=0; i<amount; i++)
                        {
                                v3s16 cp(
-                                       /*(rand()%(MAP_BLOCKSIZE-4))+2,
-                                       (rand()%(MAP_BLOCKSIZE-4))+2,
-                                       (rand()%(MAP_BLOCKSIZE-4))+2*/
-                                       (rand()%(MAP_BLOCKSIZE-2))+1,
-                                       (rand()%(MAP_BLOCKSIZE-2))+1,
-                                       (rand()%(MAP_BLOCKSIZE-2))+1
+                                       (myrand()%(MAP_BLOCKSIZE-2))+1,
+                                       (myrand()%(MAP_BLOCKSIZE-2))+1,
+                                       (myrand()%(MAP_BLOCKSIZE-2))+1
                                );
 
                                MapNode n;
-                               n.d = MATERIAL_MESE;
+                               n.d = CONTENT_COALSTONE;
+
+                               //dstream<<"Adding coalstone"<<std::endl;
                                
-                               if(rand()%8 == 0)
-                                       block->setNode(cp, n);
+                               //if(is_ground_content(block->getNode(cp).d))
+                               if(block->getNode(cp).d == CONTENT_STONE)
+                                       if(myrand()%8 == 0)
+                                               block->setNode(cp, n);
 
                                for(u16 i=0; i<26; i++)
                                {
-                                       if(rand()%8 == 0)
-                                               block->setNode(cp+g_26dirs[i], n);
+                                       //if(is_ground_content(block->getNode(cp+g_26dirs[i]).d))
+                                       if(block->getNode(cp+g_26dirs[i]).d == CONTENT_STONE)
+                                               if(myrand()%8 == 0)
+                                                       block->setNode(cp+g_26dirs[i], n);
                                }
                        }
                }
@@ -1662,50 +2767,36 @@ MapBlock * ServerMap::emergeBlock(
        /*
                Create a few rats in empty blocks underground
        */
-       if(is_underground && low_block_is_empty == true)
+       if(completely_underground)
        {
                //for(u16 i=0; i<2; i++)
                {
-                       v3s16 pos(8, 1, 8);
-                       RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
-                       block->addObject(obj);
+                       v3s16 cp(
+                               (myrand()%(MAP_BLOCKSIZE-2))+1,
+                               (myrand()%(MAP_BLOCKSIZE-2))+1,
+                               (myrand()%(MAP_BLOCKSIZE-2))+1
+                       );
+
+                       // Check that the place is empty
+                       //if(!is_ground_content(block->getNode(cp).d))
+                       if(1)
+                       {
+                               RatObject *obj = new RatObject(NULL, -1, intToFloat(cp));
+                               block->addObject(obj);
+                       }
                }
        }
        
-       /*
-               TODO: REMOVE
-               DEBUG
-               Add some objects to the block for testing.
-       */
-       /*if(p == v3s16(0,0,0))
-       {
-               //TestObject *obj = new TestObject(NULL, -1, v3f(BS*8,BS*8,BS*8));
-               Test2Object *obj = new Test2Object(NULL, -1, v3f(BS*8,BS*15,BS*8));
-               block->addObject(obj);
-       }*/
-       
-       /*
-       {
-               v3s16 pos(8, 11, 8);
-               SignObject *obj = new SignObject(NULL, -1, intToFloat(pos));
-               obj->setText("Moicka");
-               obj->setYaw(45);
-               block->addObject(obj);
-       }
-
-       {
-               v3s16 pos(8, 11, 8);
-               RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
-               block->addObject(obj);
-       }
-       */
-
        /*
                Add block to sector.
        */
        sector->insertBlock(block);
        
-       // An y-wise container if changed blocks
+       /*
+               Sector object stuff
+       */
+               
+       // An y-wise container of changed blocks
        core::map<s16, MapBlock*> changed_blocks_sector;
 
        /*
@@ -1718,10 +2809,37 @@ MapBlock * ServerMap::emergeBlock(
                        i.atEnd() == false; i++)
        {
                v3s16 p = i.getNode()->getKey();
+               v2s16 p2d(p.X,p.Z);
                u8 d = i.getNode()->getValue();
 
-               //v3s16 p = p_sector - v3s16(0, block_y*MAP_BLOCKSIZE, 0);
+               // Ground level point (user for stuff that is on ground)
+               v3s16 gp = p;
+               bool ground_found = true;
                
+               // Search real ground level
+               try{
+                       for(;;)
+                       {
+                               MapNode n = sector->getNode(gp);
+
+                               // If not air, go one up and continue to placing the tree
+                               if(n.d != CONTENT_AIR)
+                               {
+                                       gp += v3s16(0,1,0);
+                                       break;
+                               }
+
+                               // If air, go one down
+                               gp += v3s16(0,-1,0);
+                       }
+               }catch(InvalidPositionException &e)
+               {
+                       // Ground not found.
+                       ground_found = false;
+                       // This is most close to ground
+                       gp += v3s16(0,1,0);
+               }
+
                try
                {
 
@@ -1731,47 +2849,71 @@ MapBlock * ServerMap::emergeBlock(
                                        p + v3s16(0,0,0), &changed_blocks_sector))
                        {
                                MapNode n;
-                               n.d = MATERIAL_LIGHT;
+                               n.d = CONTENT_TORCH;
                                sector->setNode(p, n);
                                objects_to_remove.push_back(p);
                        }
                }
                else if(d == SECTOR_OBJECT_TREE_1)
                {
-                       v3s16 p_min = p + v3s16(-1,0,-1);
-                       v3s16 p_max = p + v3s16(1,4,1);
+                       if(ground_found == false)
+                               continue;
+
+                       v3s16 p_min = gp + v3s16(-1,0,-1);
+                       v3s16 p_max = gp + v3s16(1,5,1);
                        if(sector->isValidArea(p_min, p_max,
                                        &changed_blocks_sector))
                        {
                                MapNode n;
-                               n.d = MATERIAL_TREE;
-                               sector->setNode(p+v3s16(0,0,0), n);
-                               sector->setNode(p+v3s16(0,1,0), n);
-                               sector->setNode(p+v3s16(0,2,0), n);
-                               sector->setNode(p+v3s16(0,3,0), n);
-
-                               n.d = MATERIAL_LEAVES;
-
-                               sector->setNode(p+v3s16(0,4,0), n);
+                               n.d = CONTENT_TREE;
+                               sector->setNode(gp+v3s16(0,0,0), n);
+                               sector->setNode(gp+v3s16(0,1,0), n);
+                               sector->setNode(gp+v3s16(0,2,0), n);
+                               sector->setNode(gp+v3s16(0,3,0), n);
+
+                               n.d = CONTENT_LEAVES;
+
+                               if(rand()%4!=0) sector->setNode(gp+v3s16(0,5,0), n);
+
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,0), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,0), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(0,5,-1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(0,5,1), n);
+                               /*if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(-1,5,-1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(1,5,-1), n);*/
+
+                               sector->setNode(gp+v3s16(0,4,0), n);
                                
-                               sector->setNode(p+v3s16(-1,4,0), n);
-                               sector->setNode(p+v3s16(1,4,0), n);
-                               sector->setNode(p+v3s16(0,4,-1), n);
-                               sector->setNode(p+v3s16(0,4,1), n);
-                               sector->setNode(p+v3s16(1,4,1), n);
-                               sector->setNode(p+v3s16(-1,4,1), n);
-                               sector->setNode(p+v3s16(-1,4,-1), n);
-                               sector->setNode(p+v3s16(1,4,-1), n);
-
-                               sector->setNode(p+v3s16(-1,3,0), n);
-                               sector->setNode(p+v3s16(1,3,0), n);
-                               sector->setNode(p+v3s16(0,3,-1), n);
-                               sector->setNode(p+v3s16(0,3,1), n);
-                               sector->setNode(p+v3s16(1,3,1), n);
-                               sector->setNode(p+v3s16(-1,3,1), n);
-                               sector->setNode(p+v3s16(-1,3,-1), n);
-                               sector->setNode(p+v3s16(1,3,-1), n);
+                               sector->setNode(gp+v3s16(-1,4,0), n);
+                               sector->setNode(gp+v3s16(1,4,0), n);
+                               sector->setNode(gp+v3s16(0,4,-1), n);
+                               sector->setNode(gp+v3s16(0,4,1), n);
+                               sector->setNode(gp+v3s16(1,4,1), n);
+                               sector->setNode(gp+v3s16(-1,4,1), n);
+                               sector->setNode(gp+v3s16(-1,4,-1), n);
+                               sector->setNode(gp+v3s16(1,4,-1), n);
+
+                               sector->setNode(gp+v3s16(-1,3,0), n);
+                               sector->setNode(gp+v3s16(1,3,0), n);
+                               sector->setNode(gp+v3s16(0,3,-1), n);
+                               sector->setNode(gp+v3s16(0,3,1), n);
+                               sector->setNode(gp+v3s16(1,3,1), n);
+                               sector->setNode(gp+v3s16(-1,3,1), n);
+                               sector->setNode(gp+v3s16(-1,3,-1), n);
+                               sector->setNode(gp+v3s16(1,3,-1), n);
                                
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,0), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,0), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(0,2,-1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(0,2,1), n);
+                               /*if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(-1,2,-1), n);
+                               if(rand()%3!=0) sector->setNode(gp+v3s16(1,2,-1), n);*/
+                               
+                               // Objects are identified by wanted position
                                objects_to_remove.push_back(p);
                                
                                // Lighting has to be recalculated for this one.
@@ -1781,14 +2923,82 @@ MapBlock * ServerMap::emergeBlock(
                }
                else if(d == SECTOR_OBJECT_BUSH_1)
                {
-                       if(sector->isValidArea(p + v3s16(0,0,0),
-                                       p + v3s16(0,0,0), &changed_blocks_sector))
+                       if(ground_found == false)
+                               continue;
+                       
+                       if(sector->isValidArea(gp + v3s16(0,0,0),
+                                       gp + v3s16(0,0,0), &changed_blocks_sector))
+                       {
+                               MapNode n;
+                               n.d = CONTENT_LEAVES;
+                               sector->setNode(gp+v3s16(0,0,0), n);
+                               
+                               // Objects are identified by wanted position
+                               objects_to_remove.push_back(p);
+                       }
+               }
+               else if(d == SECTOR_OBJECT_RAVINE)
+               {
+                       s16 maxdepth = -20;
+                       v3s16 p_min = p + v3s16(-6,maxdepth,-6);
+                       v3s16 p_max = p + v3s16(6,6,6);
+                       if(sector->isValidArea(p_min, p_max,
+                                       &changed_blocks_sector))
                        {
                                MapNode n;
-                               n.d = MATERIAL_LEAVES;
-                               sector->setNode(p+v3s16(0,0,0), n);
+                               n.d = CONTENT_STONE;
+                               MapNode n2;
+                               n2.d = CONTENT_AIR;
+                               s16 depth = maxdepth + (myrand()%10);
+                               s16 z = 0;
+                               s16 minz = -6 - (-2);
+                               s16 maxz = 6 -1;
+                               for(s16 x=-6; x<=6; x++)
+                               {
+                                       z += -1 + (myrand()%3);
+                                       if(z < minz)
+                                               z = minz;
+                                       if(z > maxz)
+                                               z = maxz;
+                                       for(s16 y=depth+(myrand()%2); y<=6; y++)
+                                       {
+                                               /*std::cout<<"("<<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
+                                                               <<std::endl;*/
+                                               {
+                                                       v3s16 p2 = p + v3s16(x,y,z-2);
+                                                       if(is_ground_content(sector->getNode(p2).d)
+                                                                       && !is_mineral(sector->getNode(p2).d))
+                                                               sector->setNode(p2, n);
+                                               }
+                                               {
+                                                       v3s16 p2 = p + v3s16(x,y,z-1);
+                                                       if(is_ground_content(sector->getNode(p2).d)
+                                                                       && !is_mineral(sector->getNode(p2).d))
+                                                               sector->setNode(p2, n2);
+                                               }
+                                               {
+                                                       v3s16 p2 = p + v3s16(x,y,z+0);
+                                                       if(is_ground_content(sector->getNode(p2).d)
+                                                                       && !is_mineral(sector->getNode(p2).d))
+                                                               sector->setNode(p2, n2);
+                                               }
+                                               {
+                                                       v3s16 p2 = p + v3s16(x,y,z+1);
+                                                       if(is_ground_content(sector->getNode(p2).d)
+                                                                       && !is_mineral(sector->getNode(p2).d))
+                                                               sector->setNode(p2, n);
+                                               }
+
+                                               //if(sector->getNode(p+v3s16(x,y,z+1)).solidness()==2)
+                                               //if(p.Y+y <= sector->getGroundHeight(p2d+v2s16(x,z-2))+0.5)
+                                       }
+                               }
                                
                                objects_to_remove.push_back(p);
+                               
+                               // Lighting has to be recalculated for this one.
+                               sector->getBlocksInArea(p_min, p_max, 
+                                               lighting_invalidated_blocks);
                        }
                }
                else
@@ -1803,7 +3013,7 @@ MapBlock * ServerMap::emergeBlock(
                {
                        dstream<<"WARNING: "<<__FUNCTION_NAME
                                        <<": while inserting object "<<(int)d
-                                       <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                                       <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<"):"
                                        <<" InvalidPositionException.what()="
                                        <<e.what()<<std::endl;
                        // This is not too fatal and seems to happen sometimes.
@@ -1817,6 +3027,34 @@ MapBlock * ServerMap::emergeBlock(
                objects->remove(*i);
        }
 
+       /*
+               Initially update sunlight
+       */
+       
+       {
+               core::map<v3s16, bool> light_sources;
+               bool black_air_left = false;
+               bool bottom_invalid =
+                               block->propagateSunlight(light_sources, true,
+                               &black_air_left, true);
+
+               // If sunlight didn't reach everywhere and part of block is
+               // above ground, lighting has to be properly updated
+               if(black_air_left && some_part_underground)
+               {
+                       lighting_invalidated_blocks[block->getPos()] = block;
+               }
+
+               if(bottom_invalid)
+               {
+                       lighting_invalidated_blocks[block->getPos()] = block;
+               }
+       }
+
+       /*
+               Translate sector's changed blocks to global changed blocks
+       */
+       
        for(core::map<s16, MapBlock*>::Iterator
                        i = changed_blocks_sector.getIterator();
                        i.atEnd() == false; i++)
@@ -1826,6 +3064,33 @@ MapBlock * ServerMap::emergeBlock(
                changed_blocks.insert(block->getPos(), block);
        }
 
+       /*
+               Debug information
+       */
+       if(0)
+       {
+               dstream
+               <<"lighting_invalidated_blocks.size()"
+               <<", has_dungeons"
+               <<", completely_ug"
+               <<", some_part_ug"
+               <<"  "<<lighting_invalidated_blocks.size()
+               <<", "<<has_dungeons
+               <<", "<<completely_underground
+               <<", "<<some_part_underground
+               <<std::endl;
+       }
+
+       /*
+               Debug mode operation
+       */
+       bool haxmode = g_settings.getBool("haxmode");
+       if(haxmode)
+       {
+               // Don't calculate lighting at all
+               //lighting_invalidated_blocks.clear();
+       }
+
        return block;
 }
 
@@ -1940,20 +3205,15 @@ void ServerMap::save(bool only_changed)
 
        }//sectorlock
        
-       u32 deleted_count = 0;
-       deleted_count = deleteUnusedSectors
-                       (SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT);
-       
        /*
                Only print if something happened or saved whole map
        */
        if(only_changed == false || sector_meta_count != 0
-                       || block_count != 0 || deleted_count != 0)
+                       || block_count != 0)
        {
                dstream<<DTIME<<"ServerMap: Written: "
                                <<sector_meta_count<<" sector metadata files, "
-                               <<block_count<<" block files, "
-                               <<deleted_count<<" sectors unloaded from memory."
+                               <<block_count<<" block files"
                                <<std::endl;
        }
 }
@@ -2063,7 +3323,7 @@ void ServerMap::loadMasterHeightmap()
        if(m_heightmap != NULL)
                delete m_heightmap;
                
-       m_heightmap = UnlimitedHeightmap::deSerialize(is);
+       m_heightmap = UnlimitedHeightmap::deSerialize(is, &m_padb);
 }
 
 void ServerMap::saveSectorMeta(ServerMapSector *sector)
@@ -2233,6 +3493,9 @@ void ServerMap::saveBlock(MapBlock *block)
 void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector)
 {
        DSTACK(__FUNCTION_NAME);
+
+       try{
+
        // Block file is map/sectors/xxxxxxxx/xxxx
        std::string fullpath = m_savedir+"/sectors/"+sectordir+"/"+blockfile;
        std::ifstream is(fullpath.c_str(), std::ios_base::binary);
@@ -2264,7 +3527,8 @@ void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSecto
                block = sector->createBlankBlockNoInsert(p3d.Y);
                created_new = true;
        }
-
+       
+       // deserialize block data
        block->deSerialize(is, version);
        
        /*
@@ -2272,7 +3536,7 @@ void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSecto
        */
        if(version >= 9)
        {
-               block->updateObjects(is, version, NULL);
+               block->updateObjects(is, version, NULL, 0);
        }
 
        if(created_new)
@@ -2282,37 +3546,22 @@ void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSecto
                Convert old formats to new and save
        */
 
-       if(version == 0 || version == 1)
+       // Save old format blocks in new format
+       if(version < SER_FMT_VER_HIGHEST)
        {
-               dstream<<"Block ("<<p3d.X<<","<<p3d.Y<<","<<p3d.Z<<")"
-                               " is in old format. Updating lighting and saving"
-                               " modified blocks in new format."<<std::endl;
+               saveBlock(block);
+       }
+       
+       // We just loaded it from the disk, so it's up-to-date.
+       block->resetChangedFlag();
 
-               // Old version has zero lighting, update it
-               core::map<v3s16, MapBlock*> blocks_changed;
-               blocks_changed.insert(block->getPos(), block);
-               core::map<v3s16, MapBlock*> modified_blocks;
-               updateLighting(blocks_changed, modified_blocks);
-               
-               // Close input file
-               is.close();
-               
-               // Save modified blocks
-               core::map<v3s16, MapBlock * >::Iterator i = modified_blocks.getIterator();
-               for(; i.atEnd() == false; i++)
-               {
-                       MapBlock *b2 = i.getNode()->getValue();
-                       saveBlock(b2);
-               }
        }
-       // Save blocks in new format
-       else if(version < SER_FMT_VER_HIGHEST)
+       catch(SerializationError &e)
        {
-               saveBlock(block);
+               dstream<<"WARNING: Invalid block data on disk "
+                               "(SerializationError). Ignoring."
+                               <<std::endl;
        }
-       
-       // We just loaded it from the disk, so it's up-to-date.
-       block->resetChangedFlag();
 }
 
 // Gets from master heightmap
@@ -2341,13 +3590,15 @@ void ServerMap::PrintInfo(std::ostream &out)
        out<<"ServerMap: ";
 }
 
+#ifndef SERVER
+
 /*
        ClientMap
 */
 
 ClientMap::ClientMap(
                Client *client,
-               video::SMaterial *materials,
+               MapDrawControl &control,
                scene::ISceneNode* parent,
                scene::ISceneManager* mgr,
                s32 id
@@ -2355,9 +3606,11 @@ ClientMap::ClientMap(
        Map(dout_client),
        scene::ISceneNode(parent, mgr, id),
        m_client(client),
-       m_materials(materials),
-       mesh(NULL)
+       mesh(NULL),
+       m_control(control)
 {
+       mesh_mutex.Init();
+
        /*m_box = core::aabbox3d<f32>(0,0,0,
                        map->getW()*BS, map->getH()*BS, map->getD()*BS);*/
        /*m_box = core::aabbox3d<f32>(0,0,0,
@@ -2366,8 +3619,8 @@ ClientMap::ClientMap(
                        map->getSizeNodes().Z * BS);*/
        m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
                        BS*1000000,BS*1000000,BS*1000000);
-
-       mesh_mutex.Init();
+       
+       //setPosition(v3f(BS,BS,BS));
 }
 
 ClientMap::~ClientMap()
@@ -2429,41 +3682,23 @@ void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
        sector->deSerialize(is);
 }
 
-void ClientMap::renderMap(video::IVideoDriver* driver,
-       video::SMaterial *materials, s32 pass)
+void ClientMap::OnRegisterSceneNode()
+{
+       if(IsVisible)
+       {
+               SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
+               SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
+       }
+
+       ISceneNode::OnRegisterSceneNode();
+}
+
+void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 {
        //m_dout<<DTIME<<"Rendering map..."<<std::endl;
        DSTACK(__FUNCTION_NAME);
 
        bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
-#if 0
-       /*
-               Draw master heightmap mesh
-       */
-       
-       {
-               JMutexAutoLock lock(mesh_mutex);
-               if(mesh != NULL)
-               {
-                       u32 c = mesh->getMeshBufferCount();
-
-                       for(u32 i=0; i<c; i++)
-                       {
-                               scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
-                               const video::SMaterial& material = buf->getMaterial();
-                               video::IMaterialRenderer* rnd =
-                                               driver->getMaterialRenderer(material.MaterialType);
-                               bool transparent = (rnd && rnd->isTransparent());
-                               // Render transparent on transparent pass and likewise.
-                               if(transparent == is_transparent_pass)
-                               {
-                                       driver->setMaterial(buf->getMaterial());
-                                       driver->drawMeshBuffer(buf);
-                               }
-                       }
-               }
-       }
-#endif
 
        /*
                Get time for measuring timeout.
@@ -2473,20 +3708,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
        */
        int time1 = time(0);
 
-       /*
-               Collect all blocks that are in the view range
-
-               Should not optimize more here as we want to auto-update
-               all changed nodes in viewing range at the next step.
-       */
-
-       s16 viewing_range_nodes;
-       bool viewing_range_all;
-       {
-               JMutexAutoLock lock(g_range_mutex);
-               viewing_range_nodes = g_viewing_range_nodes;
-               viewing_range_all = g_viewing_range_all;
-       }
+       u32 daynight_ratio = m_client->getDayNightRatio();
 
        m_camera_mutex.Lock();
        v3f camera_position = m_camera_position;
@@ -2502,7 +3724,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
                        camera_position.Y / BS,
                        camera_position.Z / BS);
 
-       v3s16 box_nodes_d = viewing_range_nodes * v3s16(1,1,1);
+       v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
 
        v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
        v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
@@ -2519,16 +3741,21 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
        
        u32 vertex_count = 0;
        
-       core::map<v2s16, MapSector*>::Iterator si;
+       // For limiting number of mesh updates per frame
+       u32 mesh_update_count = 0;
+       
+       u32 blocks_would_have_drawn = 0;
+       u32 blocks_drawn = 0;
 
        //NOTE: The sectors map should be locked but we're not doing it
        // because it'd cause too much delays
 
+       int timecheck_counter = 0;
+       core::map<v2s16, MapSector*>::Iterator si;
        si = m_sectors.getIterator();
        for(; si.atEnd() == false; si++)
        {
                {
-                       static int timecheck_counter = 0;
                        timecheck_counter++;
                        if(timecheck_counter > 50)
                        {
@@ -2546,7 +3773,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
                MapSector *sector = si.getNode()->getValue();
                v2s16 sp = sector->getPos();
                
-               if(viewing_range_all == false)
+               if(m_control.range_all == false)
                {
                        if(sp.X < p_blocks_min.X
                        || sp.X > p_blocks_max.X
@@ -2572,6 +3799,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
                                if not seen on display
                        */
                        
+                       float range = 100000 * BS;
+                       if(m_control.range_all == false)
+                               range = m_control.wanted_range * BS;
+
+                       if(isBlockInSight(block->getPos(), camera_position,
+                                       camera_direction, range) == false)
+                       {
+                               continue;
+                       }
+
+#if 0                  
                        v3s16 blockpos_nodes = block->getPosRelative();
                        
                        // Block center position
@@ -2590,10 +3828,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
                        // Total distance
                        f32 d = blockpos_relative.getLength();
                        
-                       if(viewing_range_all == false)
+                       if(m_control.range_all == false)
                        {
                                // If block is far away, don't draw it
-                               if(d > viewing_range_nodes * BS)
+                               if(d > m_control.wanted_range * BS)
                                        continue;
                        }
                        
@@ -2618,23 +3856,108 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
                                if(cosangle < cos(FOV_ANGLE/2. * 4./3.))
                                        continue;
                        }
+#endif                 
+
+                       v3s16 blockpos_nodes = block->getPosRelative();
+                       
+                       // Block center position
+                       v3f blockpos(
+                                       ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
+                                       ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
+                                       ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
+                       );
+
+                       // Block position relative to camera
+                       v3f blockpos_relative = blockpos - camera_position;
+
+                       // Total distance
+                       f32 d = blockpos_relative.getLength();
                        
+#if 1
                        /*
-                               Draw the faces of the block
+                               Update expired mesh
+                       */
+
+                       bool mesh_expired = false;
+                       
+                       {
+                               JMutexAutoLock lock(block->mesh_mutex);
+
+                               mesh_expired = block->getMeshExpired();
+
+                               // Mesh has not been expired and there is no mesh:
+                               // block has no content
+                               if(block->mesh == NULL && mesh_expired == false)
+                                       continue;
+                       }
+
+                       f32 faraway = BS*50;
+                       //f32 faraway = m_control.wanted_range * BS;
+                       
+                       /*
+                               This has to be done with the mesh_mutex unlocked
                        */
+                       // Pretty random but this should work somewhat nicely
+                       if(mesh_expired && (
+                                       (mesh_update_count < 3
+                                               && (d < faraway || mesh_update_count < 2)
+                                       )
+                                       || 
+                                       (m_control.range_all && mesh_update_count < 20)
+                               )
+                       )
+                       /*if(mesh_expired && mesh_update_count < 6
+                                       && (d < faraway || mesh_update_count < 3))*/
+                       {
+                               mesh_update_count++;
+
+                               // Mesh has been expired: generate new mesh
+                               //block->updateMeshes(daynight_i);
+                               block->updateMesh(daynight_ratio);
+
+                               mesh_expired = false;
+                       }
                        
+                       /*
+                               Don't draw an expired mesh that is far away
+                       */
+                       /*if(mesh_expired && d >= faraway)
+                       //if(mesh_expired)
+                       {
+                               // Instead, delete it
+                               JMutexAutoLock lock(block->mesh_mutex);
+                               if(block->mesh)
+                               {
+                                       block->mesh->drop();
+                                       block->mesh = NULL;
+                               }
+                               // And continue to next block
+                               continue;
+                       }*/
+#endif
+                       /*
+                               Draw the faces of the block
+                       */
                        {
                                JMutexAutoLock lock(block->mesh_mutex);
 
-                               // Cancel if block has no mesh
-                               if(block->mesh == NULL)
+                               scene::SMesh *mesh = block->mesh;
+
+                               if(mesh == NULL)
                                        continue;
+                               
+                               blocks_would_have_drawn++;
+                               if(blocks_drawn >= m_control.wanted_max_blocks
+                                               && m_control.range_all == false
+                                               && d > m_control.wanted_min_range * BS)
+                                       continue;
+                               blocks_drawn++;
 
-                               u32 c = block->mesh->getMeshBufferCount();
+                               u32 c = mesh->getMeshBufferCount();
 
                                for(u32 i=0; i<c; i++)
                                {
-                                       scene::IMeshBuffer *buf = block->mesh->getMeshBuffer(i);
+                                       scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
                                        const video::SMaterial& material = buf->getMaterial();
                                        video::IMaterialRenderer* rnd =
                                                        driver->getMaterialRenderer(material.MaterialType);
@@ -2650,205 +3973,265 @@ void ClientMap::renderMap(video::IVideoDriver* driver,
                        }
                } // foreach sectorblocks
        }
+       
+       m_control.blocks_drawn = blocks_drawn;
+       m_control.blocks_would_have_drawn = blocks_would_have_drawn;
 
        /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
                        <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
 }
 
-void ClientMap::updateMesh()
+v3s16 ClientMap::setTempMod(v3s16 p, NodeMod mod, bool *changed)
 {
-#if 0
-       DSTACK(__FUNCTION_NAME);
-       //TODO
        /*
-               Check what sectors don't draw anything useful at ground level
-               and create a mesh of the rough heightmap at those positions.
+               Add it to all blocks touching it
        */
+       v3s16 dirs[7] = {
+               v3s16(0,0,0), // this
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+       for(u16 i=0; i<7; i++)
+       {
+               v3s16 p2 = p + dirs[i];
+               // Block position of neighbor (or requested) node
+               v3s16 blockpos = getNodeBlockPos(p2);
+               MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
+               if(blockref == NULL)
+                       continue;
+               // Relative position of requested node
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+               if(blockref->setTempMod(relpos, mod))
+               {
+                       if(changed != NULL)
+                               *changed = true;
+               }
+       }
+       return getNodeBlockPos(p);
+}
+v3s16 ClientMap::clearTempMod(v3s16 p, bool *changed)
+{
+       v3s16 dirs[7] = {
+               v3s16(0,0,0), // this
+               v3s16(0,0,1), // back
+               v3s16(0,1,0), // top
+               v3s16(1,0,0), // right
+               v3s16(0,0,-1), // front
+               v3s16(0,-1,0), // bottom
+               v3s16(-1,0,0), // left
+       };
+       for(u16 i=0; i<7; i++)
+       {
+               v3s16 p2 = p + dirs[i];
+               // Block position of neighbor (or requested) node
+               v3s16 blockpos = getNodeBlockPos(p2);
+               MapBlock * blockref = getBlockNoCreateNoEx(blockpos);
+               if(blockref == NULL)
+                       continue;
+               // Relative position of requested node
+               v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+               if(blockref->clearTempMod(relpos))
+               {
+                       if(changed != NULL)
+                               *changed = true;
+               }
+       }
+       return getNodeBlockPos(p);
+}
 
-       m_camera_mutex.Lock();
-       v3f camera_position = m_camera_position;
-       v3f camera_direction = m_camera_direction;
-       m_camera_mutex.Unlock();
+void ClientMap::PrintInfo(std::ostream &out)
+{
+       out<<"ClientMap: ";
+}
 
-       v3s16 cam_pos_nodes(
-                       camera_position.X / BS,
-                       camera_position.Y / BS,
-                       camera_position.Z / BS);
+#endif // !SERVER
 
-       v3s16 box_nodes_d = HEIGHTMAP_RANGE_NODES * v3s16(1,1,1);
+/*
+       MapVoxelManipulator
+*/
 
-       v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
-       v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
+MapVoxelManipulator::MapVoxelManipulator(Map *map)
+{
+       m_map = map;
+}
 
-       // Take a fair amount as we will be dropping more out later
-       v3s16 p_blocks_min(
-                       p_nodes_min.X / MAP_BLOCKSIZE - 1,
-                       p_nodes_min.Y / MAP_BLOCKSIZE - 1,
-                       p_nodes_min.Z / MAP_BLOCKSIZE - 1);
-       v3s16 p_blocks_max(
-                       p_nodes_max.X / MAP_BLOCKSIZE + 1,
-                       p_nodes_max.Y / MAP_BLOCKSIZE + 1,
-                       p_nodes_max.Z / MAP_BLOCKSIZE + 1);
-       
-       /*
-               Initialize new mesh
-       */
-       
-       scene::SMesh *mesh_new = new scene::SMesh();
-       //scene::IMeshBuffer *buf = NULL;
-       scene::SMeshBuffer *buf = NULL;
+MapVoxelManipulator::~MapVoxelManipulator()
+{
+       /*dstream<<"MapVoxelManipulator: blocks: "<<m_loaded_blocks.size()
+                       <<std::endl;*/
+}
 
-       u8 material_in_use = 0;
+#if 1
+void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id)
+{
+       TimeTaker timer1("emerge", &emerge_time);
 
-       /*
-               Loop through sectors
-       */
-       
-       for(core::map<v2s16, MapSector*>::Iterator
-                       si = m_sectors.getIterator();
-                       si.atEnd() == false; si++)
-       {
-               MapSector *sector = si.getNode()->getValue();
-               
-               if(sector->getId() != MAPSECTOR_CLIENT)
-               {
-                       dstream<<"WARNING: Client has a non-client sector"
-                                       <<std::endl;
-                       continue;
-               }
-               
-               ClientMapSector *cs = (ClientMapSector*)sector;
+       // Units of these are MapBlocks
+       v3s16 p_min = getNodeBlockPos(a.MinEdge);
+       v3s16 p_max = getNodeBlockPos(a.MaxEdge);
 
-               v2s16 sp = sector->getPos();
+       VoxelArea block_area_nodes
+                       (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
+
+       addArea(block_area_nodes);
 
-               if(sp.X < p_blocks_min.X
-               || sp.X > p_blocks_max.X
-               || sp.Y < p_blocks_min.Z
-               || sp.Y > p_blocks_max.Z)
+       for(s32 z=p_min.Z; z<=p_max.Z; z++)
+       for(s32 y=p_min.Y; y<=p_max.Y; y++)
+       for(s32 x=p_min.X; x<=p_max.X; x++)
+       {
+               v3s16 p(x,y,z);
+               core::map<v3s16, bool>::Node *n;
+               n = m_loaded_blocks.find(p);
+               if(n != NULL)
                        continue;
                
-               /*
-                       Get some ground level info
-               */
-               
-               s16 a = -5;
-
-               s16 cn[4] = 
+               bool block_data_inexistent = false;
+               try
                {
-                       cs->getCorner(0)+a,
-                       cs->getCorner(1)+a,
-                       cs->getCorner(2)+a,
-                       cs->getCorner(3)+a,
-               };
-               s16 cn_avg = (cn[0]+cn[1]+cn[2]+cn[3])/4;
-               s16 cn_min = 32767;
-               s16 cn_max = -32768;
-               for(s16 i=0; i<4; i++)
+                       TimeTaker timer1("emerge load", &emerge_load_time);
+
+                       /*dstream<<"Loading block (caller_id="<<caller_id<<")"
+                                       <<" ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                                       <<" wanted area: ";
+                       a.print(dstream);
+                       dstream<<std::endl;*/
+                       
+                       MapBlock *block = m_map->getBlockNoCreate(p);
+                       if(block->isDummy())
+                               block_data_inexistent = true;
+                       else
+                               block->copyTo(*this);
+               }
+               catch(InvalidPositionException &e)
                {
-                       if(cn[i] < cn_min)
-                               cn_min = cn[i];
-                       if(cn[i] > cn_max)
-                               cn_max = cn[i];
+                       block_data_inexistent = true;
                }
-               s16 cn_slope = cn_max - cn_min;
-               
-               /*
-                       Generate this part of the heightmap mesh
-               */
 
-               u8 material;
-               if(cn_avg + MAP_BLOCKSIZE/4 <= WATER_LEVEL)
-                       material = 0;
-               else if(cn_slope <= MAP_BLOCKSIZE)
-                       material = 1;
-               else
-                       material = 2;
-
-               if(material != material_in_use || buf == NULL)
+               if(block_data_inexistent)
                {
-                       // Try to get a meshbuffer associated with the material
-                       buf = (scene::SMeshBuffer*)mesh_new->getMeshBuffer
-                                       (g_mesh_materials[material]);
-                       // If not found, create one
-                       if(buf == NULL)
+                       VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
+                       // Fill with VOXELFLAG_INEXISTENT
+                       for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++)
+                       for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++)
                        {
-                               // This is a "Standard MeshBuffer",
-                               // it's a typedeffed CMeshBuffer<video::S3DVertex>
-                               buf = new scene::SMeshBuffer();
-
-                               // Set material
-                               buf->Material = g_mesh_materials[material];
-                               // Use VBO
-                               //buf->setHardwareMappingHint(scene::EHM_STATIC);
-                               // Add to mesh
-                               mesh_new->addMeshBuffer(buf);
-                               // Mesh grabbed it
-                               buf->drop();
+                               s32 i = m_area.index(a.MinEdge.X,y,z);
+                               memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE);
                        }
-                       material_in_use = material;
                }
 
-               // Sector side width in floating-point units
-               f32 sd = BS * MAP_BLOCKSIZE;
-               // Sector position in global floating-point units
-               v3f spf = v3f((f32)sp.X, 0, (f32)sp.Y) * sd;
+               m_loaded_blocks.insert(p, true);
+       }
+
+       //dstream<<"emerge done"<<std::endl;
+}
+#endif
 
-               //video::SColor c(255,255,255,255);
-               u8 cc = 180;
-               video::SColor c(255,cc,cc,cc);
-               
-               video::S3DVertex vertices[4] =
+#if 0
+void MapVoxelManipulator::emerge(VoxelArea a)
+{
+       TimeTaker timer1("emerge", &emerge_time);
+       
+       v3s16 size = a.getExtent();
+       
+       VoxelArea padded = a;
+       padded.pad(m_area.getExtent() / 4);
+       addArea(padded);
+
+       for(s16 z=0; z<size.Z; z++)
+       for(s16 y=0; y<size.Y; y++)
+       for(s16 x=0; x<size.X; x++)
+       {
+               v3s16 p(x,y,z);
+               s32 i = m_area.index(a.MinEdge + p);
+               // Don't touch nodes that have already been loaded
+               if(!(m_flags[i] & VOXELFLAG_NOT_LOADED))
+                       continue;
+               try
                {
-                       video::S3DVertex(spf.X,   (f32)BS*cn[0],spf.Z,   0,0,0, c, 0,1),
-                       video::S3DVertex(spf.X+sd,(f32)BS*cn[1],spf.Z,   0,0,0, c, 1,1),
-                       video::S3DVertex(spf.X+sd,(f32)BS*cn[2],spf.Z+sd,0,0,0, c, 1,0),
-                       video::S3DVertex(spf.X,   (f32)BS*cn[3],spf.Z+sd,0,0,0, c, 0,0),
-               };
-               u16 indices[] = {0,1,2,2,3,0};
-               
-               buf->append(vertices, 4, indices, 6);
+                       TimeTaker timer1("emerge load", &emerge_load_time);
+                       MapNode n = m_map->getNode(a.MinEdge + p);
+                       m_data[i] = n;
+                       m_flags[i] = 0;
+               }
+               catch(InvalidPositionException &e)
+               {
+                       m_flags[i] = VOXELFLAG_INEXISTENT;
+               }
        }
-       
-       // Set VBO on
-       //mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
+}
+#endif
+
 
+/*
+       TODO: Add an option to only update eg. water and air nodes.
+             This will make it interfere less with important stuff if
+                 run on background.
+*/
+void MapVoxelManipulator::blitBack
+               (core::map<v3s16, MapBlock*> & modified_blocks)
+{
+       if(m_area.getExtent() == v3s16(0,0,0))
+               return;
+       
+       //TimeTaker timer1("blitBack");
+       
        /*
-               Replace the mesh
+               Initialize block cache
        */
+       v3s16 blockpos_last;
+       MapBlock *block = NULL;
+       bool block_checked_in_modified = false;
 
-       mesh_mutex.Lock();
+       for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++)
+       for(s32 y=m_area.MinEdge.Y; y<=m_area.MaxEdge.Y; y++)
+       for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++)
+       {
+               v3s16 p(x,y,z);
 
-       scene::SMesh *mesh_old = mesh;
+               u8 f = m_flags[m_area.index(p)];
+               if(f & (VOXELFLAG_NOT_LOADED|VOXELFLAG_INEXISTENT))
+                       continue;
 
-       //DEBUG
-       /*mesh = NULL;
-       mesh_new->drop();*/
-       mesh = mesh_new;
-       
-       mesh_mutex.Unlock();
+               MapNode &n = m_data[m_area.index(p)];
+                       
+               v3s16 blockpos = getNodeBlockPos(p);
+               
+               try
+               {
+                       // Get block
+                       if(block == NULL || blockpos != blockpos_last){
+                               block = m_map->getBlockNoCreate(blockpos);
+                               blockpos_last = blockpos;
+                               block_checked_in_modified = false;
+                       }
+                       
+                       // Calculate relative position in block
+                       v3s16 relpos = p - blockpos * MAP_BLOCKSIZE;
 
-       if(mesh_old != NULL)
-       {
-               /*dstream<<"mesh_old refcount="<<mesh_old->getReferenceCount()
-                               <<std::endl;
-               scene::IMeshBuffer *buf = mesh_new->getMeshBuffer
-                               (g_materials[MATERIAL_GRASS]);
-               if(buf != NULL)
-                       dstream<<"grass buf refcount="<<buf->getReferenceCount()
-                                       <<std::endl;*/
+                       // Don't continue if nothing has changed here
+                       if(block->getNode(relpos) == n)
+                               continue;
 
-               mesh_old->drop();
-       }
-       else
-       {
-               dstream<<"WARNING: There was no old master heightmap mesh"<<std::endl;
+                       //m_map->setNode(m_area.MinEdge + p, n);
+                       block->setNode(relpos, n);
+                       
+                       /*
+                               Make sure block is in modified_blocks
+                       */
+                       if(block_checked_in_modified == false)
+                       {
+                               modified_blocks[blockpos] = block;
+                               block_checked_in_modified = true;
+                       }
+               }
+               catch(InvalidPositionException &e)
+               {
+               }
        }
-#endif
-}
-
-void ClientMap::PrintInfo(std::ostream &out)
-{
-       out<<"ClientMap: ";
 }
 
-
+//END