Merge branch 'upstream/master'
[oweals/minetest.git] / src / map.cpp
index d8cc34ca7808ce3e0df302464aa52ea23e91336b..e1769b8eff897718ace3b053b00cdf9c20a3bd08 100644 (file)
@@ -26,11 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "utility.h"
 #include "voxel.h"
 #include "porting.h"
-#include "mineral.h"
-#include "noise.h"
-#include "serverobject.h"
-#include "content_mapnode.h"
 #include "mapgen.h"
+#include "nodemetadata.h"
 
 extern "C" {
        #include "sqlite3.h"
@@ -141,19 +138,6 @@ MapBlock * Map::getBlockNoCreate(v3s16 p3d)
        return block;
 }
 
-
-/*MapBlock * Map::getBlockCreate(v3s16 p3d)
-{
-       v2s16 p2d(p3d.X, p3d.Z);
-       MapSector * sector = getSectorCreate(p2d);
-       assert(sector);
-       MapBlock *block = sector->getBlockNoCreate(p3d.Y);
-       if(block)
-               return block;
-       block = sector->createBlankBlock(p3d.Y);
-       return block;
-}*/
-
 bool Map::isNodeUnderground(v3s16 p)
 {
        v3s16 blockpos = getNodeBlockPos(p);
@@ -167,6 +151,45 @@ bool Map::isNodeUnderground(v3s16 p)
        }
 }
 
+bool Map::isValidPosition(v3s16 p)
+{
+       v3s16 blockpos = getNodeBlockPos(p);
+       MapBlock *block = getBlockNoCreate(blockpos);
+       return (block != NULL);
+}
+
+// Returns a CONTENT_IGNORE node if not found
+MapNode Map::getNodeNoEx(v3s16 p)
+{
+       v3s16 blockpos = getNodeBlockPos(p);
+       MapBlock *block = getBlockNoCreateNoEx(blockpos);
+       if(block == NULL)
+               return MapNode(CONTENT_IGNORE);
+       v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+       return block->getNodeNoCheck(relpos);
+}
+
+// throws InvalidPositionException if not found
+MapNode Map::getNode(v3s16 p)
+{
+       v3s16 blockpos = getNodeBlockPos(p);
+       MapBlock *block = getBlockNoCreateNoEx(blockpos);
+       if(block == NULL)
+               throw InvalidPositionException();
+       v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+       return block->getNodeNoCheck(relpos);
+}
+
+// throws InvalidPositionException if not found
+void Map::setNode(v3s16 p, MapNode & n)
+{
+       v3s16 blockpos = getNodeBlockPos(p);
+       MapBlock *block = getBlockNoCreate(blockpos);
+       v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+       block->setNodeNoCheck(relpos, n);
+}
+
+
 /*
        Goes recursively through the neighbours of the node.
 
@@ -732,6 +755,25 @@ void Map::updateLighting(enum LightBank bank,
 
                }
        }
+       
+       /*
+               Enable this to disable proper lighting for speeding up map
+               generation for testing or whatever
+       */
+#if 0
+       //if(g_settings.get(""))
+       {
+               core::map<v3s16, MapBlock*>::Iterator i;
+               i = blocks_to_update.getIterator();
+               for(; i.atEnd() == false; i++)
+               {
+                       MapBlock *block = i.getNode()->getValue();
+                       v3s16 p = block->getPos();
+                       block->setLightingExpired(false);
+               }
+               return;
+       }
+#endif
 
 #if 0
        {
@@ -874,7 +916,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
        {
        }
 
-#if 1
+#if 0
        /*
                If the new node is solid and there is grass below, change it to mud
        */
@@ -1044,7 +1086,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                v3s16 p2 = p + dirs[i];
 
                MapNode n2 = getNode(p2);
-               if(content_liquid(n2.d))
+               if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
                {
                        m_transforming_liquid.push_back(p2);
                }
@@ -1198,17 +1240,19 @@ void Map::removeNodeAndUpdate(v3s16 p,
        }
 
        /*
-               Add neighboring liquid nodes to transform queue.
+               Add neighboring liquid nodes and this node to transform queue.
+               (it's vital for the node itself to get updated last.)
        */
-       v3s16 dirs[6] = {
+       v3s16 dirs[7] = {
                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
+               v3s16(0,0,0), // self
        };
-       for(u16 i=0; i<6; i++)
+       for(u16 i=0; i<7; i++)
        {
                try
                {
@@ -1216,7 +1260,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
                v3s16 p2 = p + dirs[i];
 
                MapNode n2 = getNode(p2);
-               if(content_liquid(n2.d))
+               if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
                {
                        m_transforming_liquid.push_back(p2);
                }
@@ -1344,9 +1388,14 @@ bool Map::dayNightDiffed(v3s16 blockpos)
 /*
        Updates usage timers
 */
-void Map::timerUpdate(float dtime)
+void Map::timerUpdate(float dtime, float unload_timeout,
+               core::list<v3s16> *unloaded_blocks)
 {
-       //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
+       bool save_before_unloading = (mapType() == MAPTYPE_SERVER);
+       
+       core::list<v2s16> sector_deletion_queue;
+       u32 deleted_blocks_count = 0;
+       u32 saved_blocks_count = 0;
 
        core::map<v2s16, MapSector*>::Iterator si;
 
@@ -1355,48 +1404,85 @@ void Map::timerUpdate(float dtime)
        {
                MapSector *sector = si.getNode()->getValue();
 
+               bool all_blocks_deleted = true;
+
                core::list<MapBlock*> blocks;
                sector->getBlocks(blocks);
                for(core::list<MapBlock*>::Iterator i = blocks.begin();
                                i != blocks.end(); i++)
                {
-                       (*i)->incrementUsageTimer(dtime);
+                       MapBlock *block = (*i);
+                       
+                       block->incrementUsageTimer(dtime);
+                       
+                       if(block->getUsageTimer() > unload_timeout)
+                       {
+                               v3s16 p = block->getPos();
+
+                               // Save if modified
+                               if(block->getModified() != MOD_STATE_CLEAN
+                                               && save_before_unloading)
+                               {
+                                       saveBlock(block);
+                                       saved_blocks_count++;
+                               }
+
+                               // Delete from memory
+                               sector->deleteBlock(block);
+
+                               if(unloaded_blocks)
+                                       unloaded_blocks->push_back(p);
+
+                               deleted_blocks_count++;
+                       }
+                       else
+                       {
+                               all_blocks_deleted = false;
+                       }
+               }
+
+               if(all_blocks_deleted)
+               {
+                       sector_deletion_queue.push_back(si.getNode()->getKey());
                }
        }
+       
+       // Finally delete the empty sectors
+       deleteSectors(sector_deletion_queue);
+       
+       if(deleted_blocks_count != 0)
+       {
+               PrintInfo(dstream); // ServerMap/ClientMap:
+               dstream<<"Unloaded "<<deleted_blocks_count
+                               <<" blocks from memory";
+               if(save_before_unloading)
+                       dstream<<", of which "<<saved_blocks_count<<" were written";
+               dstream<<"."<<std::endl;
+       }
 }
 
-void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
+void Map::deleteSectors(core::list<v2s16> &list)
 {
        core::list<v2s16>::Iterator j;
        for(j=list.begin(); j!=list.end(); j++)
        {
                MapSector *sector = m_sectors[*j];
-               if(only_blocks)
-               {
-                       sector->deleteBlocks();
-               }
-               else
-               {
-                       /*
-                               If sector is in sector cache, remove it from there
-                       */
-                       if(m_sector_cache == sector)
-                       {
-                               m_sector_cache = NULL;
-                       }
-                       /*
-                               Remove from map and delete
-                       */
-                       m_sectors.remove(*j);
-                       delete sector;
-               }
+               // If sector is in sector cache, remove it from there
+               if(m_sector_cache == sector)
+                       m_sector_cache = NULL;
+               // Remove from map and delete
+               m_sectors.remove(*j);
+               delete sector;
        }
 }
 
-u32 Map::unloadUnusedData(float timeout, bool only_blocks,
+#if 0
+void Map::unloadUnusedData(float timeout,
                core::list<v3s16> *deleted_blocks)
 {
        core::list<v2s16> sector_deletion_queue;
+       u32 deleted_blocks_count = 0;
+       u32 saved_blocks_count = 0;
 
        core::map<v2s16, MapSector*>::Iterator si = m_sectors.getIterator();
        for(; si.atEnd() == false; si++)
@@ -1411,14 +1497,18 @@ u32 Map::unloadUnusedData(float timeout, bool only_blocks,
                                i != blocks.end(); i++)
                {
                        MapBlock *block = (*i);
-
+                       
                        if(block->getUsageTimer() > timeout)
                        {
                                // Save if modified
                                if(block->getModified() != MOD_STATE_CLEAN)
+                               {
                                        saveBlock(block);
+                                       saved_blocks_count++;
+                               }
                                // Delete from memory
                                sector->deleteBlock(block);
+                               deleted_blocks_count++;
                        }
                        else
                        {
@@ -1432,37 +1522,16 @@ u32 Map::unloadUnusedData(float timeout, bool only_blocks,
                }
        }
 
-#if 0
-       core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
-       for(; i.atEnd() == false; i++)
-       {
-               MapSector *sector = i.getNode()->getValue();
-               /*
-                       Delete sector from memory if it hasn't been used in a long time
-               */
-               if(sector->usage_timer > timeout)
-               {
-                       sector_deletion_queue.push_back(i.getNode()->getKey());
+       deleteSectors(sector_deletion_queue);
 
-                       if(deleted_blocks != NULL)
-                       {
-                               // Collect positions of blocks of sector
-                               MapSector *sector = i.getNode()->getValue();
-                               core::list<MapBlock*> blocks;
-                               sector->getBlocks(blocks);
-                               for(core::list<MapBlock*>::Iterator i = blocks.begin();
-                                               i != blocks.end(); i++)
-                               {
-                                       deleted_blocks->push_back((*i)->getPos());
-                               }
-                       }
-               }
-       }
-#endif
+       dstream<<"Map: Unloaded "<<deleted_blocks_count<<" blocks from memory"
+                       <<", of which "<<saved_blocks_count<<" were wr."
+                       <<std::endl;
 
-       deleteSectors(sector_deletion_queue, only_blocks);
-       return sector_deletion_queue.getSize();
+       //return sector_deletion_queue.getSize();
+       //return deleted_blocks_count;
 }
+#endif
 
 void Map::PrintInfo(std::ostream &out)
 {
@@ -1471,6 +1540,17 @@ void Map::PrintInfo(std::ostream &out)
 
 #define WATER_DROP_BOOST 4
 
+enum NeighborType {
+       NEIGHBOR_UPPER,
+       NEIGHBOR_SAME_LEVEL,
+       NEIGHBOR_LOWER
+};
+struct NodeNeighbor {
+       MapNode n;
+       NeighborType t;
+       v3s16 p;
+};
+
 void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 {
        DSTACK(__FUNCTION_NAME);
@@ -1489,262 +1569,221 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                */
                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);
-
+               MapNode n0 = getNodeNoEx(p0);
+                               
                /*
-                       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;
+                       Collect information about current node
+                */
+               s8 liquid_level = -1;
+               u8 liquid_kind = CONTENT_IGNORE;
+               LiquidType liquid_type = content_features(n0.d).liquid_type;
+               switch (liquid_type) {
+                       case LIQUID_SOURCE:
+                               liquid_level = 8;
+                               liquid_kind = content_features(n0.d).liquid_alternative_flowing;
+                               break;
+                       case LIQUID_FLOWING:
+                               liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
+                               liquid_kind = n0.d;
+                               break;
+                       case LIQUID_NONE:
+                               // if this is an air node, it *could* be transformed into a liquid. otherwise,
+                               // continue with the next node.
+                               if (n0.d != CONTENT_AIR)
+                                       continue;
+                               liquid_kind = CONTENT_AIR;
+                               break;
+               }
+               
+               /*
+                       Collect information about the environment
+                */
+               v3s16 dirs[6] = {
+                       v3s16( 0, 1, 0), // top
+                       v3s16( 0,-1, 0), // bottom
+                       v3s16( 1, 0, 0), // right
+                       v3s16(-1, 0, 0), // left
+                       v3s16( 0, 0, 1), // back
+                       v3s16( 0, 0,-1), // front
+               };
+               NodeNeighbor sources[6]; // surrounding sources
+               int num_sources = 0;
+               NodeNeighbor flows[6]; // surrounding flowing liquid nodes
+               int num_flows = 0;
+               NodeNeighbor airs[6]; // surrounding air
+               int num_airs = 0;
+               NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid
+               int num_neutrals = 0;
+               bool flowing_down = false;
+               for (u16 i = 0; i < 6; i++) {
+                       NeighborType nt = NEIGHBOR_SAME_LEVEL;
+                       switch (i) {
+                               case 0:
+                                       nt = NEIGHBOR_UPPER;
+                                       break;
+                               case 1:
+                                       nt = NEIGHBOR_LOWER;
+                                       break;
+                       }
+                       v3s16 npos = p0 + dirs[i];
+                       NodeNeighbor nb = {getNodeNoEx(npos), nt, npos};
+                       switch (content_features(nb.n.d).liquid_type) {
+                               case LIQUID_NONE:
+                                       if (nb.n.d == CONTENT_AIR) {
+                                               airs[num_airs++] = nb;
+                                               // if the current node happens to be a flowing node, it will start to flow down here.
+                                               if (nb.t == NEIGHBOR_LOWER)
+                                                       flowing_down = true;
+                                       } else {
+                                               neutrals[num_neutrals++] = nb;
                                        }
-                                       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;
+                                       break;
+                               case LIQUID_SOURCE:
+                                       // if this node is not (yet) of a liquid type, choose the first liquid type we encounter 
+                                       if (liquid_kind == CONTENT_AIR)
+                                               liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
+                                       if (content_features(nb.n.d).liquid_alternative_flowing !=liquid_kind) {
+                                               neutrals[num_neutrals++] = nb;
+                                       } else {
+                                               sources[num_sources++] = nb;
                                        }
-                                       else if(n2_liquid_level > 0)
-                                       {
-                                               new_liquid_level = n2_liquid_level - 1;
+                                       break;
+                               case LIQUID_FLOWING:
+                                       // if this node is not (yet) of a liquid type, choose the first liquid type we encounter
+                                       if (liquid_kind == CONTENT_AIR)
+                                               liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
+                                       if (content_features(nb.n.d).liquid_alternative_flowing != liquid_kind) {
+                                               neutrals[num_neutrals++] = nb;
+                                       } else {
+                                               flows[num_flows++] = nb;
+                                               if (nb.t == NEIGHBOR_LOWER)
+                                                       flowing_down = true;
                                        }
-
-                                       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);
+                                       break;
+                       }
+               }
+               
+               /*
+                       decide on the type (and possibly level) of the current node
+                */
+               u8 new_node_content;
+               s8 new_node_level = -1;
+               if (num_sources >= 2 || liquid_type == LIQUID_SOURCE) {
+                       // liquid_kind will be set to either the flowing alternative of the node (if it's a liquid)
+                       // or the flowing alternative of the first of the surrounding sources (if it's air), so
+                       // it's perfectly safe to use liquid_kind here to determine the new node content.
+                       new_node_content = content_features(liquid_kind).liquid_alternative_source;
+               } else if (num_sources == 1 && sources[0].t != NEIGHBOR_LOWER) {
+                       // liquid_kind is set properly, see above
+                       new_node_content = liquid_kind;
+                       new_node_level = 7;
+               } else {
+                       // no surrounding sources, so get the maximum level that can flow into this node
+                       for (u16 i = 0; i < num_flows; i++) {
+                               u8 nb_liquid_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
+                               switch (flows[i].t) {
+                                       case NEIGHBOR_UPPER:
+                                               if (nb_liquid_level + WATER_DROP_BOOST > new_node_level) {
+                                                       new_node_level = 7;
+                                                       if (nb_liquid_level + WATER_DROP_BOOST < 7)
+                                                               new_node_level = nb_liquid_level + WATER_DROP_BOOST;
+                                               }
+                                               break;
+                                       case NEIGHBOR_LOWER:
+                                               break;
+                                       case NEIGHBOR_SAME_LEVEL:
+                                               if ((flows[i].n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK &&
+                                                       nb_liquid_level > 0 && nb_liquid_level - 1 > new_node_level) {
+                                                       new_node_level = nb_liquid_level - 1;
+                                               }
+                                               break;
                                }
-
-                               /*
-                                       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)
-                                       {
+                       }
+                       // don't flow as far in open terrain - if there isn't at least one adjacent solid block,
+                       // substract another unit from the resulting water level.
+                       if (!flowing_down && new_node_level >= 1) {
+                               bool at_wall = false;
+                               for (u16 i = 0; i < num_neutrals; i++) {
+                                       if (neutrals[i].t == NEIGHBOR_SAME_LEVEL) {
+                                               at_wall = true;
+                                               break;
                                        }
                                }
+                               if (!at_wall)
+                                       new_node_level -= 1;
                        }
+                       
+                       if (new_node_level >= 0)
+                               new_node_content = liquid_kind;
+                       else
+                               new_node_content = CONTENT_AIR;
                }
-
-               // Get a new one from queue if the node has turned into non-water
-               if(content_liquid(n0.d) == false)
+               
+               /*
+                       check if anything has changed. if not, just continue with the next node.
+                */
+               if (new_node_content == n0.d && (content_features(n0.d).liquid_type != LIQUID_FLOWING ||
+                                                                                ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level &&
+                                                                                ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK)
+                                                                                == flowing_down)))
                        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);
-                       //dstream<<"[1] n2.param="<<(int)n2.param<<std::endl;
-
-                       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;
+                       update the current node
+                */
+               bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
+               n0.d = new_node_content;
+               if (content_features(n0.d).liquid_type == LIQUID_FLOWING) {
+                       // set level to last 3 bits, flowing down bit to 4th bit
+                       n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK);
+               } else {
+                       n0.param2 = 0;
+               }
+               setNode(p0, n0);
+               v3s16 blockpos = getNodeBlockPos(p0);
+               MapBlock *block = getBlockNoCreateNoEx(blockpos);
+               if(block != NULL)
+                       modified_blocks.insert(blockpos, block);
+               
+               /*
+                       enqueue neighbors for update if neccessary
+                */
+               switch (content_features(n0.d).liquid_type) {
+                       case LIQUID_SOURCE:
+                               // make sure source flows into all neighboring nodes
+                               for (u16 i = 0; i < num_flows; i++)
+                                       if (flows[i].t != NEIGHBOR_UPPER)
+                                               m_transforming_liquid.push_back(flows[i].p);
+                               for (u16 i = 0; i < num_airs; i++)
+                                       if (airs[i].t != NEIGHBOR_UPPER)
+                                               m_transforming_liquid.push_back(airs[i].p);
+                               break;
+                       case LIQUID_NONE:
+                               // this flow has turned to air; neighboring flows might need to do the same
+                               for (u16 i = 0; i < num_flows; i++)
+                                       m_transforming_liquid.push_back(flows[i].p);
+                               break;
+                       case LIQUID_FLOWING:
+                               for (u16 i = 0; i < num_flows; i++) {
+                                       u8 flow_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
+                                       // liquid_level is still the ORIGINAL level of this node.
+                                       if (flows[i].t != NEIGHBOR_UPPER && ((flow_level < liquid_level || flow_level < new_node_level) ||
+                                               flow_down_enabled))
+                                               m_transforming_liquid.push_back(flows[i].p);
                                }
-                               else
-                               {
-                                       if(liquid_next_level > liquid_level)
-                                       {
-                                               n2.param2 = liquid_next_level;
-                                               setNode(p2, n2);
-
-                                               n2_changed = true;
-                                               flowed = true;
-                                       }
+                               for (u16 i = 0; i < num_airs; i++) {
+                                       if (airs[i].t != NEIGHBOR_UPPER && (airs[i].t == NEIGHBOR_LOWER || new_node_level > 0))
+                                               m_transforming_liquid.push_back(airs[i].p);
                                }
-                       }
-                       else if(n2.d == CONTENT_AIR)
-                       {
-                               n2.d = nonsource_c;
-                               n2.param2 = liquid_next_level;
-                               setNode(p2, n2);
-
-                               n2_changed = true;
-                               flowed = true;
-                       }
-
-                       //dstream<<"[2] n2.param="<<(int)n2.param<<std::endl;
-
-                       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 && !is_source)
                                break;
-
-                       }catch(InvalidPositionException &e)
-                       {
-                       }
                }
-
+               
                loopcount++;
                //if(loopcount >= 100000)
-               if(loopcount >= initial_size * 1)
+               if(loopcount >= initial_size * 10) {
                        break;
+               }
        }
        //dstream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl;
 }
@@ -1934,7 +1973,6 @@ ServerMap::~ServerMap()
        {
                if(m_map_saving_enabled)
                {
-                       //save(false);
                        // Save only changed parts
                        save(true);
                        dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
@@ -1967,7 +2005,15 @@ void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos)
 {
        /*dstream<<"initBlockMake(): ("<<blockpos.X<<","<<blockpos.Y<<","
                        <<blockpos.Z<<")"<<std::endl;*/
-
+       
+       // Do nothing if not inside limits (+-1 because of neighbors)
+       if(blockpos_over_limit(blockpos - v3s16(1,1,1)) ||
+               blockpos_over_limit(blockpos + v3s16(1,1,1)))
+       {
+               data->no_op = true;
+               return;
+       }
+       
        data->no_op = false;
        data->seed = m_seed;
        data->blockpos = blockpos;
@@ -1988,9 +2034,14 @@ void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos)
 
                        for(s16 y=-1; y<=1; y++)
                        {
-                               MapBlock *block = createBlock(blockpos);
+                               //MapBlock *block = createBlock(blockpos);
+                               // 1) get from memory, 2) load from disk
+                               MapBlock *block = emergeBlock(blockpos, false);
+                               // 3) create a blank one
+                               if(block == NULL)
+                                       block = createBlock(blockpos);
 
-                               // Lighting won't be calculated
+                               // Lighting will not be valid after make_chunk is called
                                block->setLightingExpired(true);
                                // Lighting will be calculated
                                //block->setLightingExpired(false);
@@ -2013,6 +2064,7 @@ void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos)
                neighboring blocks
        */
        
+       // The area that contains this block and it's neighbors
        v3s16 bigarea_blocks_min = blockpos - v3s16(1,1,1);
        v3s16 bigarea_blocks_max = blockpos + v3s16(1,1,1);
        
@@ -2037,10 +2089,12 @@ MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data,
 
        if(data->no_op)
        {
-               dstream<<"finishBlockMake(): no-op"<<std::endl;
+               //dstream<<"finishBlockMake(): no-op"<<std::endl;
                return NULL;
        }
 
+       bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+
        /*dstream<<"Resulting vmanip:"<<std::endl;
        data->vmanip.print(dstream);*/
        
@@ -2053,10 +2107,11 @@ MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data,
                //TimeTaker timer("finishBlockMake() blitBackAll");
                data->vmanip->blitBackAll(&changed_blocks);
        }
-#if 1
-       dstream<<"finishBlockMake: changed_blocks.size()="
-                       <<changed_blocks.size()<<std::endl;
-#endif
+
+       if(enable_mapgen_debug_info)
+               dstream<<"finishBlockMake: changed_blocks.size()="
+                               <<changed_blocks.size()<<std::endl;
+
        /*
                Copy transforming liquid information
        */
@@ -2090,28 +2145,46 @@ MapBlock* ServerMap::finishBlockMake(mapgen::BlockMakeData *data,
        /*
                NOTE: Lighting and object adding shouldn't really be here, but
                lighting is a bit tricky to move properly to makeBlock.
-               TODO: Do this the right way anyway.
+               TODO: Do this the right way anyway, that is, move it to makeBlock.
+                     - There needs to be some way for makeBlock to report back if
+                           the lighting update is going further down because of the
+                               new block blocking light
        */
 
        /*
                Update lighting
+               NOTE: This takes ~60ms, TODO: Investigate why
        */
+       {
+               TimeTaker t("finishBlockMake lighting update");
 
-       core::map<v3s16, MapBlock*> lighting_update_blocks;
-       // Center block
-       lighting_update_blocks.insert(block->getPos(), block);
+               core::map<v3s16, MapBlock*> lighting_update_blocks;
+#if 1
+               // Center block
+               lighting_update_blocks.insert(block->getPos(), block);
+#endif
 #if 0
-       // All modified blocks
-       for(core::map<v3s16, MapBlock*>::Iterator
-                       i = changed_blocks.getIterator();
-                       i.atEnd() == false; i++)
-       {
-               lighting_update_blocks.insert(i.getNode()->getKey(),
-                               i.getNode()->getValue());
-       }
+               // All modified blocks
+               // NOTE: Should this be done? If this is not done, then the lighting
+               // of the others will be updated in a different place, one by one, i
+               // think... or they might not? Well, at least they are left marked as
+               // "lighting expired"; it seems that is not handled at all anywhere,
+               // so enabling this will slow it down A LOT because otherwise it
+               // would not do this at all. This causes the black trees.
+               for(core::map<v3s16, MapBlock*>::Iterator
+                               i = changed_blocks.getIterator();
+                               i.atEnd() == false; i++)
+               {
+                       lighting_update_blocks.insert(i.getNode()->getKey(),
+                                       i.getNode()->getValue());
+               }
 #endif
-       updateLighting(lighting_update_blocks, changed_blocks);
-       
+               updateLighting(lighting_update_blocks, changed_blocks);
+
+               if(enable_mapgen_debug_info == false)
+                       t.stop(true); // Hide output
+       }
+
        /*
                Add random objects to block
        */
@@ -2224,6 +2297,8 @@ MapBlock * ServerMap::generateBlock(
                        <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
                        <<std::endl;*/
        
+       bool enable_mapgen_debug_info = g_settings.getBool("enable_mapgen_debug_info");
+
        TimeTaker timer("generateBlock");
        
        //MapBlock *block = original_dummy;
@@ -2252,6 +2327,9 @@ MapBlock * ServerMap::generateBlock(
        {
                TimeTaker t("mapgen::make_block()");
                mapgen::make_block(&data);
+
+               if(enable_mapgen_debug_info == false)
+                       t.stop(true); // Hide output
        }
 
        /*
@@ -2263,53 +2341,61 @@ MapBlock * ServerMap::generateBlock(
                Get central block
        */
        MapBlock *block = getBlockNoCreateNoEx(p);
-       assert(block);
 
 #if 0
        /*
                Check result
        */
-       bool erroneus_content = false;
-       for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
-       for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
-       for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+       if(block)
        {
-               v3s16 p(x0,y0,z0);
-               MapNode n = block->getNode(p);
-               if(n.d == CONTENT_IGNORE)
+               bool erroneus_content = false;
+               for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+               for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+               for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+               {
+                       v3s16 p(x0,y0,z0);
+                       MapNode n = block->getNode(p);
+                       if(n.d == CONTENT_IGNORE)
+                       {
+                               dstream<<"CONTENT_IGNORE at "
+                                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                                               <<std::endl;
+                               erroneus_content = true;
+                               assert(0);
+                       }
+               }
+               if(erroneus_content)
                {
-                       dstream<<"CONTENT_IGNORE at "
-                                       <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
-                                       <<std::endl;
-                       erroneus_content = true;
                        assert(0);
                }
        }
-       if(erroneus_content)
-       {
-               assert(0);
-       }
 #endif
 
 #if 0
        /*
                Generate a completely empty block
        */
-       for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
-       for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+       if(block)
        {
-               for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+               for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+               for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
                {
-                       MapNode n;
-                       if(y0%2==0)
-                               n.d = CONTENT_AIR;
-                       else
-                               n.d = CONTENT_STONE;
-                       block->setNode(v3s16(x0,y0,z0), n);
+                       for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
+                       {
+                               MapNode n;
+                               if(y0%2==0)
+                                       n.d = CONTENT_AIR;
+                               else
+                                       n.d = CONTENT_STONE;
+                               block->setNode(v3s16(x0,y0,z0), n);
+                       }
                }
        }
 #endif
 
+       if(enable_mapgen_debug_info == false)
+               timer.stop(true); // Hide output
+
        return block;
 }
 
@@ -2376,23 +2462,51 @@ MapBlock * ServerMap::createBlock(v3s16 p)
        return block;
 }
 
-#if 0
-MapBlock * ServerMap::emergeBlock(
-               v3s16 p,
-               bool only_from_disk,
-               core::map<v3s16, MapBlock*> &changed_blocks,
-               core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
-)
+MapBlock * ServerMap::emergeBlock(v3s16 p, bool allow_generate)
 {
-       DSTACKF("%s: p=(%d,%d,%d), only_from_disk=%d",
+       DSTACKF("%s: p=(%d,%d,%d), allow_generate=%d",
                        __FUNCTION_NAME,
-                       p.X, p.Y, p.Z, only_from_disk);
+                       p.X, p.Y, p.Z, allow_generate);
        
-       // This has to be redone or removed
-       assert(0);
+       {
+               MapBlock *block = getBlockNoCreateNoEx(p);
+               if(block)
+                       return block;
+       }
+
+       {
+               MapBlock *block = loadBlock(p);
+               if(block)
+                       return block;
+       }
+
+       if(allow_generate)
+       {
+               core::map<v3s16, MapBlock*> modified_blocks;
+               MapBlock *block = generateBlock(p, modified_blocks);
+               if(block)
+               {
+                       MapEditEvent event;
+                       event.type = MEET_OTHER;
+                       event.p = p;
+
+                       // Copy modified_blocks to event
+                       for(core::map<v3s16, MapBlock*>::Iterator
+                                       i = modified_blocks.getIterator();
+                                       i.atEnd()==false; i++)
+                       {
+                               event.modified_blocks.insert(i.getNode()->getKey(), false);
+                       }
+
+                       // Queue event
+                       dispatchEvent(&event);
+                                                               
+                       return block;
+               }
+       }
+
        return NULL;
 }
-#endif
 
 #if 0
        /*
@@ -2839,10 +2953,10 @@ MapSector* ServerMap::loadSectorMeta(std::string sectordir, bool save_after_load
                // format. Just go ahead and create the sector.
                if(fs::PathExists(sectordir))
                {
-                       dstream<<"ServerMap::loadSectorMeta(): Sector metafile "
+                       /*dstream<<"ServerMap::loadSectorMeta(): Sector metafile "
                                        <<fullpath<<" doesn't exist but directory does."
                                        <<" Continuing with a sector with no metadata."
-                                       <<std::endl;
+                                       <<std::endl;*/
                        sector = new ServerMapSector(this, p2d);
                        m_sectors.insert(p2d, sector);
                }
@@ -3312,13 +3426,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
        // 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);
+                       p_nodes_min.X / MAP_BLOCKSIZE - 2,
+                       p_nodes_min.Y / MAP_BLOCKSIZE - 2,
+                       p_nodes_min.Z / MAP_BLOCKSIZE - 2);
        v3s16 p_blocks_max(
-                       p_nodes_max.X / MAP_BLOCKSIZE,
-                       p_nodes_max.Y / MAP_BLOCKSIZE,
-                       p_nodes_max.Z / MAP_BLOCKSIZE);
+                       p_nodes_max.X / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Z / MAP_BLOCKSIZE + 1);
        
        u32 vertex_count = 0;
        
@@ -3390,6 +3504,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                        {
                                continue;
                        }
+
+                       // Okay, this block will be drawn. Reset usage timer.
+                       block->resetUsageTimer();
                        
                        // This is ugly (spherical distance limit?)
                        /*if(m_control.range_all == false &&