ToolDefManager serialization
[oweals/minetest.git] / src / map.cpp
index 4c79c0ac45eaa170c4ecb9ea26bcd22b3d32bb34..2dfbbbb33d9108f451180b86f02f009bbc5152b2 100644 (file)
@@ -21,7 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mapsector.h"
 #include "mapblock.h"
 #include "main.h"
+#ifndef SERVER
 #include "client.h"
+#endif
 #include "filesys.h"
 #include "utility.h"
 #include "voxel.h"
@@ -34,6 +36,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #endif
 #include "settings.h"
 #include "log.h"
+#include "profiler.h"
+#include "nodedef.h"
+#include "gamedef.h"
+
+#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
 
 /*
        SQLite format specification:
@@ -56,8 +63,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
        Map
 */
 
-Map::Map(std::ostream &dout):
+Map::Map(std::ostream &dout, IGameDef *gamedef):
        m_dout(dout),
+       m_gamedef(gamedef),
        m_sector_cache(NULL)
 {
        /*m_sector_mutex.Init();
@@ -227,6 +235,8 @@ void Map::unspreadLight(enum LightBank bank,
                core::map<v3s16, bool> & light_sources,
                core::map<v3s16, MapBlock*>  & modified_blocks)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        v3s16 dirs[6] = {
                v3s16(0,0,1), // back
                v3s16(0,1,0), // top
@@ -323,19 +333,20 @@ void Map::unspreadLight(enum LightBank bank,
                                        If the neighbor is dimmer than what was specified
                                        as oldlight (the light of the previous node)
                                */
-                               if(n2.getLight(bank) < oldlight)
+                               if(n2.getLight(bank, nodemgr) < oldlight)
                                {
                                        /*
                                                And the neighbor is transparent and it has some light
                                        */
-                                       if(n2.light_propagates() && n2.getLight(bank) != 0)
+                                       if(nodemgr->get(n2).light_propagates
+                                                       && n2.getLight(bank, nodemgr) != 0)
                                        {
                                                /*
                                                        Set light to 0 and add to queue
                                                */
 
-                                               u8 current_light = n2.getLight(bank);
-                                               n2.setLight(bank, 0);
+                                               u8 current_light = n2.getLight(bank, nodemgr);
+                                               n2.setLight(bank, 0, nodemgr);
                                                block->setNode(relpos, n2);
 
                                                unlighted_nodes.insert(n2pos, current_light);
@@ -409,6 +420,8 @@ void Map::spreadLight(enum LightBank bank,
                core::map<v3s16, bool> & from_nodes,
                core::map<v3s16, MapBlock*> & modified_blocks)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        const v3s16 dirs[6] = {
                v3s16(0,0,1), // back
                v3s16(0,1,0), // top
@@ -467,7 +480,7 @@ void Map::spreadLight(enum LightBank bank,
                // Get node straight from the block
                MapNode n = block->getNode(relpos);
 
-               u8 oldlight = n.getLight(bank);
+               u8 oldlight = n.getLight(bank, nodemgr);
                u8 newlight = diminish_light(oldlight);
 
                // Loop through 6 neighbors
@@ -505,7 +518,7 @@ void Map::spreadLight(enum LightBank bank,
                                        If the neighbor is brighter than the current node,
                                        add to list (it will light up this node on its turn)
                                */
-                               if(n2.getLight(bank) > undiminish_light(oldlight))
+                               if(n2.getLight(bank, nodemgr) > undiminish_light(oldlight))
                                {
                                        lighted_nodes.insert(n2pos, true);
                                        //lighted_nodes.push_back(n2pos);
@@ -515,11 +528,11 @@ void Map::spreadLight(enum LightBank bank,
                                        If the neighbor is dimmer than how much light this node
                                        would spread on it, add to list
                                */
-                               if(n2.getLight(bank) < newlight)
+                               if(n2.getLight(bank, nodemgr) < newlight)
                                {
-                                       if(n2.light_propagates())
+                                       if(nodemgr->get(n2).light_propagates)
                                        {
-                                               n2.setLight(bank, newlight);
+                                               n2.setLight(bank, newlight, nodemgr);
                                                block->setNode(relpos, n2);
                                                lighted_nodes.insert(n2pos, true);
                                                //lighted_nodes.push_back(n2pos);
@@ -568,6 +581,8 @@ void Map::lightNeighbors(enum LightBank bank,
 
 v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        v3s16 dirs[6] = {
                v3s16(0,0,1), // back
                v3s16(0,1,0), // top
@@ -593,8 +608,8 @@ v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
                {
                        continue;
                }
-               if(n2.getLight(bank) > brightest_light || found_something == false){
-                       brightest_light = n2.getLight(bank);
+               if(n2.getLight(bank, nodemgr) > brightest_light || found_something == false){
+                       brightest_light = n2.getLight(bank, nodemgr);
                        brightest_pos = n2pos;
                        found_something = true;
                }
@@ -617,6 +632,8 @@ v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
 s16 Map::propagateSunlight(v3s16 start,
                core::map<v3s16, MapBlock*> & modified_blocks)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        s16 y = start.Y;
        for(; ; y--)
        {
@@ -635,9 +652,9 @@ s16 Map::propagateSunlight(v3s16 start,
                v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
                MapNode n = block->getNode(relpos);
 
-               if(n.sunlight_propagates())
+               if(nodemgr->get(n).sunlight_propagates)
                {
-                       n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
+                       n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr);
                        block->setNode(relpos, n);
 
                        modified_blocks.insert(blockpos, block);
@@ -663,6 +680,8 @@ void Map::updateLighting(enum LightBank bank,
                core::map<v3s16, MapBlock*> & a_blocks,
                core::map<v3s16, MapBlock*> & modified_blocks)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        /*m_dout<<DTIME<<"Map::updateLighting(): "
                        <<a_blocks.size()<<" blocks."<<std::endl;*/
 
@@ -706,8 +725,8 @@ void Map::updateLighting(enum LightBank bank,
                                try{
                                        v3s16 p(x,y,z);
                                        MapNode n = block->getNode(v3s16(x,y,z));
-                                       u8 oldlight = n.getLight(bank);
-                                       n.setLight(bank, 0);
+                                       u8 oldlight = n.getLight(bank, nodemgr);
+                                       n.setLight(bank, 0, nodemgr);
                                        block->setNode(v3s16(x,y,z), n);
 
                                        // Collect borders for unlighting
@@ -858,11 +877,11 @@ void Map::updateLighting(enum LightBank bank,
 
                {
                        //TimeTaker timer("unSpreadLight");
-                       vmanip.unspreadLight(bank, unlight_from, light_sources);
+                       vmanip.unspreadLight(bank, unlight_from, light_sources, nodemgr);
                }
                {
                        //TimeTaker timer("spreadLight");
-                       vmanip.spreadLight(bank, light_sources);
+                       vmanip.spreadLight(bank, light_sources, nodemgr);
                }
                {
                        //TimeTaker timer("blitBack");
@@ -898,6 +917,8 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
 void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                core::map<v3s16, MapBlock*> &modified_blocks, std::string &player_name)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        /*PrintInfo(m_dout);
        m_dout<<DTIME<<"Map::addNodeAndUpdate(): p=("
                        <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
@@ -924,7 +945,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
        try{
                MapNode topnode = getNode(toppos);
 
-               if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
+               if(topnode.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN)
                        node_under_sunlight = false;
        }
        catch(InvalidPositionException &e)
@@ -935,7 +956,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
        /*
                If the new node is solid and there is grass below, change it to mud
        */
-       if(content_features(n).walkable == true)
+       if(nodemgr->get(n).walkable == true)
        {
                try{
                        MapNode bottomnode = getNode(bottompos);
@@ -977,7 +998,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
        {
                enum LightBank bank = banks[i];
 
-               u8 lightwas = getNode(p).getLight(bank);
+               u8 lightwas = getNode(p).getLight(bank, nodemgr);
 
                // Add the block of the added node to modified_blocks
                v3s16 blockpos = getNodeBlockPos(p);
@@ -994,16 +1015,16 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                // light again into this.
                unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
 
-               n.setLight(bank, 0);
+               n.setLight(bank, 0, nodemgr);
        }
 
        /*
                If node lets sunlight through and is under sunlight, it has
                sunlight too.
        */
-       if(node_under_sunlight && content_features(n).sunlight_propagates)
+       if(node_under_sunlight && nodemgr->get(n).sunlight_propagates)
        {
-               n.setLight(LIGHTBANK_DAY, LIGHT_SUN);
+               n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr);
        }
 
        /*
@@ -1016,10 +1037,10 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                Add intial metadata
        */
 
-       NodeMetadata *meta_proto = content_features(n).initial_metadata;
+       NodeMetadata *meta_proto = nodemgr->get(n).initial_metadata;
        if(meta_proto)
        {
-               NodeMetadata *meta = meta_proto->clone();
+               NodeMetadata *meta = meta_proto->clone(m_gamedef);
                meta->setOwner(player_name);
                setNodeMetadata(p, meta);
        }
@@ -1031,7 +1052,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                TODO: This could be optimized by mass-unlighting instead
                          of looping
        */
-       if(node_under_sunlight && !content_features(n).sunlight_propagates)
+       if(node_under_sunlight && !nodemgr->get(n).sunlight_propagates)
        {
                s16 y = p.Y - 1;
                for(;; y--){
@@ -1047,12 +1068,12 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                                break;
                        }
 
-                       if(n2.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
+                       if(n2.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN)
                        {
                                unLightNeighbors(LIGHTBANK_DAY,
-                                               n2pos, n2.getLight(LIGHTBANK_DAY),
+                                               n2pos, n2.getLight(LIGHTBANK_DAY, nodemgr),
                                                light_sources, modified_blocks);
-                               n2.setLight(LIGHTBANK_DAY, 0);
+                               n2.setLight(LIGHTBANK_DAY, 0, nodemgr);
                                setNode(n2pos, n2);
                        }
                        else
@@ -1102,7 +1123,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
                v3s16 p2 = p + dirs[i];
 
                MapNode n2 = getNode(p2);
-               if(content_liquid(n2.getContent()) || n2.getContent() == CONTENT_AIR)
+               if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
                {
                        m_transforming_liquid.push_back(p2);
                }
@@ -1118,6 +1139,8 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
 void Map::removeNodeAndUpdate(v3s16 p,
                core::map<v3s16, MapBlock*> &modified_blocks)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        /*PrintInfo(m_dout);
        m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
                        <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
@@ -1136,7 +1159,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
        try{
                MapNode topnode = getNode(toppos);
 
-               if(topnode.getLight(LIGHTBANK_DAY) != LIGHT_SUN)
+               if(topnode.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN)
                        node_under_sunlight = false;
        }
        catch(InvalidPositionException &e)
@@ -1158,7 +1181,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
                        Unlight neighbors (in case the node is a light source)
                */
                unLightNeighbors(bank, p,
-                               getNode(p).getLight(bank),
+                               getNode(p).getLight(bank, nodemgr),
                                light_sources, modified_blocks);
        }
 
@@ -1220,7 +1243,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
                // TODO: Is this needed? Lighting is cleared up there already.
                try{
                        MapNode n = getNode(p);
-                       n.setLight(LIGHTBANK_DAY, 0);
+                       n.setLight(LIGHTBANK_DAY, 0, nodemgr);
                        setNode(p, n);
                }
                catch(InvalidPositionException &e)
@@ -1276,7 +1299,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
                v3s16 p2 = p + dirs[i];
 
                MapNode n2 = getNode(p2);
-               if(content_liquid(n2.getContent()) || n2.getContent() == CONTENT_AIR)
+               if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
                {
                        m_transforming_liquid.push_back(p2);
                }
@@ -1573,6 +1596,8 @@ struct NodeNeighbor {
 
 void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        DSTACK(__FUNCTION_NAME);
        //TimeTaker timer("transformLiquids()");
 
@@ -1607,11 +1632,11 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                 */
                s8 liquid_level = -1;
                u8 liquid_kind = CONTENT_IGNORE;
-               LiquidType liquid_type = content_features(n0.getContent()).liquid_type;
+               LiquidType liquid_type = nodemgr->get(n0).liquid_type;
                switch (liquid_type) {
                        case LIQUID_SOURCE:
                                liquid_level = LIQUID_LEVEL_SOURCE;
-                               liquid_kind = content_features(n0.getContent()).liquid_alternative_flowing;
+                               liquid_kind = nodemgr->get(n0).liquid_alternative_flowing;
                                break;
                        case LIQUID_FLOWING:
                                liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
@@ -1651,7 +1676,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                        }
                        v3s16 npos = p0 + dirs[i];
                        NodeNeighbor nb = {getNodeNoEx(npos), nt, npos};
-                       switch (content_features(nb.n.getContent()).liquid_type) {
+                       switch (nodemgr->get(nb.n.getContent()).liquid_type) {
                                case LIQUID_NONE:
                                        if (nb.n.getContent() == CONTENT_AIR) {
                                                airs[num_airs++] = nb;
@@ -1671,8 +1696,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                                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.getContent()).liquid_alternative_flowing;
-                                       if (content_features(nb.n.getContent()).liquid_alternative_flowing !=liquid_kind) {
+                                               liquid_kind = nodemgr->get(nb.n.getContent()).liquid_alternative_flowing;
+                                       if (nodemgr->get(nb.n.getContent()).liquid_alternative_flowing !=liquid_kind) {
                                                neutrals[num_neutrals++] = nb;
                                        } else {
                                                sources[num_sources++] = nb;
@@ -1681,8 +1706,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                                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.getContent()).liquid_alternative_flowing;
-                                       if (content_features(nb.n.getContent()).liquid_alternative_flowing != liquid_kind) {
+                                               liquid_kind = nodemgr->get(nb.n.getContent()).liquid_alternative_flowing;
+                                       if (nodemgr->get(nb.n.getContent()).liquid_alternative_flowing != liquid_kind) {
                                                neutrals[num_neutrals++] = nb;
                                        } else {
                                                flows[num_flows++] = nb;
@@ -1703,7 +1728,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                        // 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;
+                       new_node_content = nodemgr->get(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;
@@ -1732,7 +1757,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                                }
                        }
 
-                       u8 viscosity = content_features(liquid_kind).liquid_viscosity;
+                       u8 viscosity = nodemgr->get(liquid_kind).liquid_viscosity;
                        if (viscosity > 1 && max_node_level != liquid_level) {
                                // amount to gain, limited by viscosity
                                // must be at least 1 in absolute value
@@ -1758,7 +1783,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                /*
                        check if anything has changed. if not, just continue with the next node.
                 */
-               if (new_node_content == n0.getContent() && (content_features(n0.getContent()).liquid_type != LIQUID_FLOWING ||
+               if (new_node_content == n0.getContent() && (nodemgr->get(n0.getContent()).liquid_type != LIQUID_FLOWING ||
                                                                                 ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level &&
                                                                                 ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK)
                                                                                 == flowing_down)))
@@ -1768,8 +1793,8 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                /*
                        update the current node
                 */
-               bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
-               if (content_features(new_node_content).liquid_type == LIQUID_FLOWING) {
+               //bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
+               if (nodemgr->get(new_node_content).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 {
@@ -1783,14 +1808,14 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
                if(block != NULL) {
                        modified_blocks.insert(blockpos, block);
                        // If node emits light, MapBlock requires lighting update
-                       if(content_features(n0).light_source != 0)
+                       if(nodemgr->get(n0).light_source != 0)
                                lighting_modified_blocks[block->getPos()] = block;
                }
 
                /*
                        enqueue neighbors for update if neccessary
                 */
-               switch (content_features(n0.getContent()).liquid_type) {
+               switch (nodemgr->get(n0.getContent()).liquid_type) {
                        case LIQUID_SOURCE:
                        case LIQUID_FLOWING:
                                // make sure source flows into all neighboring nodes
@@ -1819,13 +1844,18 @@ NodeMetadata* Map::getNodeMetadata(v3s16 p)
        v3s16 blockpos = getNodeBlockPos(p);
        v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
        MapBlock *block = getBlockNoCreateNoEx(blockpos);
-       if(block == NULL)
+       if(!block){
+               infostream<<"Map::getNodeMetadata(): Need to emerge "
+                               <<PP(blockpos)<<std::endl;
+               block = emergeBlock(blockpos, false);
+       }
+       if(!block)
        {
-               infostream<<"WARNING: Map::setNodeMetadata(): Block not found"
+               infostream<<"WARNING: Map::getNodeMetadata(): Block not found"
                                <<std::endl;
                return NULL;
        }
-       NodeMetadata *meta = block->m_node_metadata.get(p_rel);
+       NodeMetadata *meta = block->m_node_metadata->get(p_rel);
        return meta;
 }
 
@@ -1834,13 +1864,18 @@ void Map::setNodeMetadata(v3s16 p, NodeMetadata *meta)
        v3s16 blockpos = getNodeBlockPos(p);
        v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
        MapBlock *block = getBlockNoCreateNoEx(blockpos);
-       if(block == NULL)
+       if(!block){
+               infostream<<"Map::setNodeMetadata(): Need to emerge "
+                               <<PP(blockpos)<<std::endl;
+               block = emergeBlock(blockpos, false);
+       }
+       if(!block)
        {
                infostream<<"WARNING: Map::setNodeMetadata(): Block not found"
                                <<std::endl;
                return;
        }
-       block->m_node_metadata.set(p_rel, meta);
+       block->m_node_metadata->set(p_rel, meta);
 }
 
 void Map::removeNodeMetadata(v3s16 p)
@@ -1854,7 +1889,7 @@ void Map::removeNodeMetadata(v3s16 p)
                                <<std::endl;
                return;
        }
-       block->m_node_metadata.remove(p_rel);
+       block->m_node_metadata->remove(p_rel);
 }
 
 void Map::nodeMetadataStep(float dtime,
@@ -1879,7 +1914,7 @@ void Map::nodeMetadataStep(float dtime,
                for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
                {
                        MapBlock *block = *i;
-                       bool changed = block->m_node_metadata.step(dtime);
+                       bool changed = block->m_node_metadata->step(dtime);
                        if(changed)
                                changed_blocks[block->getPos()] = block;
                }
@@ -1890,8 +1925,8 @@ void Map::nodeMetadataStep(float dtime,
        ServerMap
 */
 
-ServerMap::ServerMap(std::string savedir):
-       Map(dout_server),
+ServerMap::ServerMap(std::string savedir, IGameDef *gamedef):
+       Map(dout_server, gamedef),
        m_seed(0),
        m_map_metadata_changed(true),
        m_database(NULL),
@@ -2065,6 +2100,7 @@ void ServerMap::initBlockMake(mapgen::BlockMakeData *data, v3s16 blockpos)
        data->no_op = false;
        data->seed = m_seed;
        data->blockpos = blockpos;
+       data->nodemgr = m_gamedef->ndef();
 
        /*
                Create the whole area of this and the neighboring blocks
@@ -2372,7 +2408,7 @@ ServerMapSector * ServerMap::createSector(v2s16 p2d)
                Generate blank sector
        */
        
-       sector = new ServerMapSector(this, p2d);
+       sector = new ServerMapSector(this, p2d, m_gamedef);
        
        // Sector position on map in nodes
        v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
@@ -2610,152 +2646,6 @@ MapBlock * ServerMap::emergeBlock(v3s16 p, bool allow_generate)
        return NULL;
 }
 
-#if 0
-       /*
-               Do not generate over-limit
-       */
-       if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
-       || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
-       || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
-       || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
-       || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
-       || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
-               throw InvalidPositionException("emergeBlock(): pos. over limit");
-       
-       v2s16 p2d(p.X, p.Z);
-       s16 block_y = p.Y;
-       /*
-               This will create or load a sector if not found in memory.
-               If block exists on disk, it will be loaded.
-       */
-       ServerMapSector *sector;
-       try{
-               sector = createSector(p2d);
-               //sector = emergeSector(p2d, changed_blocks);
-       }
-       catch(InvalidPositionException &e)
-       {
-               infostream<<"emergeBlock: createSector() failed: "
-                               <<e.what()<<std::endl;
-               infostream<<"Path to failed sector: "<<getSectorDir(p2d)
-                               <<std::endl
-                               <<"You could try to delete it."<<std::endl;
-               throw e;
-       }
-       catch(VersionMismatchException &e)
-       {
-               infostream<<"emergeBlock: createSector() failed: "
-                               <<e.what()<<std::endl;
-               infostream<<"Path to failed sector: "<<getSectorDir(p2d)
-                               <<std::endl
-                               <<"You could try to delete it."<<std::endl;
-               throw e;
-       }
-
-       /*
-               Try to get a block from the sector
-       */
-
-       bool does_not_exist = false;
-       bool lighting_expired = false;
-       MapBlock *block = sector->getBlockNoCreateNoEx(block_y);
-       
-       // If not found, try loading from disk
-       if(block == NULL)
-       {
-               block = loadBlock(p);
-       }
-       
-       // Handle result
-       if(block == NULL)
-       {
-               does_not_exist = true;
-       }
-       else if(block->isDummy() == true)
-       {
-               does_not_exist = true;
-       }
-       else if(block->getLightingExpired())
-       {
-               lighting_expired = true;
-       }
-       else
-       {
-               // Valid block
-               //infostream<<"emergeBlock(): Returning already valid block"<<std::endl;
-               return block;
-       }
-       
-       /*
-               If block was not found on disk and not going to generate a
-               new one, make sure there is a dummy block in place.
-       */
-       if(only_from_disk && (does_not_exist || lighting_expired))
-       {
-               //infostream<<"emergeBlock(): Was not on disk but not generating"<<std::endl;
-
-               if(block == NULL)
-               {
-                       // Create dummy block
-                       block = new MapBlock(this, p, true);
-
-                       // Add block to sector
-                       sector->insertBlock(block);
-               }
-               // Done.
-               return block;
-       }
-
-       //infostream<<"Not found on disk, generating."<<std::endl;
-       // 0ms
-       //TimeTaker("emergeBlock() generate");
-
-       //infostream<<"emergeBlock(): Didn't find valid block -> making one"<<std::endl;
-
-       /*
-               If the block doesn't exist, generate the block.
-       */
-       if(does_not_exist)
-       {
-               block = generateBlock(p, block, sector, changed_blocks,
-                               lighting_invalidated_blocks); 
-       }
-
-       if(lighting_expired)
-       {
-               lighting_invalidated_blocks.insert(p, block);
-       }
-
-#if 0
-       /*
-               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);
-
-               // 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)
-               if(black_air_left)
-               {
-                       lighting_invalidated_blocks[block->getPos()] = block;
-               }
-
-               if(bottom_invalid)
-               {
-                       lighting_invalidated_blocks[block->getPos()] = block;
-               }
-       }
-#endif
-       
-       return block;
-}
-#endif
-
 s16 ServerMap::findGroundLevel(v2s16 p2d)
 {
 #if 0
@@ -2820,7 +2710,7 @@ void ServerMap::verifyDatabase() {
                return;
        
        {
-               std::string dbp = m_savedir + "/map.sqlite";
+               std::string dbp = m_savedir + DIR_DELIM + "map.sqlite";
                bool needs_create = false;
                int d;
                
@@ -2854,12 +2744,18 @@ void ServerMap::verifyDatabase() {
                        throw FileNotGoodException("Cannot prepare write statement");
                }
                
+               d = sqlite3_prepare(m_database, "SELECT `pos` FROM `blocks`", -1, &m_database_list, NULL);
+               if(d != SQLITE_OK) {
+                       infostream<<"WARNING: Database list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
+                       throw FileNotGoodException("Cannot prepare read statement");
+               }
+               
                infostream<<"Server: Database opened"<<std::endl;
        }
 }
 
 bool ServerMap::loadFromFolders() {
-       if(!m_database && !fs::PathExists(m_savedir + "/map.sqlite"))
+       if(!m_database && !fs::PathExists(m_savedir + DIR_DELIM + "map.sqlite"))
                return true;
        return false;
 }
@@ -2889,13 +2785,13 @@ std::string ServerMap::getSectorDir(v2s16 pos, int layout)
                                (unsigned int)pos.X&0xffff,
                                (unsigned int)pos.Y&0xffff);
 
-                       return m_savedir + "/sectors/" + cc;
+                       return m_savedir + DIR_DELIM + "sectors" + DIR_DELIM + cc;
                case 2:
-                       snprintf(cc, 9, "%.3x/%.3x",
+                       snprintf(cc, 9, "%.3x" DIR_DELIM "%.3x",
                                (unsigned int)pos.X&0xfff,
                                (unsigned int)pos.Y&0xfff);
 
-                       return m_savedir + "/sectors2/" + cc;
+                       return m_savedir + DIR_DELIM + "sectors2" + DIR_DELIM + cc;
                default:
                        assert(false);
        }
@@ -2905,7 +2801,7 @@ v2s16 ServerMap::getSectorPos(std::string dirname)
 {
        unsigned int x, y;
        int r;
-       size_t spos = dirname.rfind('/') + 1;
+       size_t spos = dirname.rfind(DIR_DELIM_C) + 1;
        assert(spos != std::string::npos);
        if(dirname.size() - spos == 8)
        {
@@ -2915,7 +2811,7 @@ v2s16 ServerMap::getSectorPos(std::string dirname)
        else if(dirname.size() - spos == 3)
        {
                // New layout
-               r = sscanf(dirname.substr(spos-4).c_str(), "%3x/%3x", &x, &y);
+               r = sscanf(dirname.substr(spos-4).c_str(), "%3x" DIR_DELIM "%3x", &x, &y);
                // Sign-extend the 12 bit values up to 16 bits...
                if(x&0x800) x|=0xF000;
                if(y&0x800) y|=0xF000;
@@ -3026,6 +2922,52 @@ void ServerMap::save(bool only_changed)
        }
 }
 
+static s32 unsignedToSigned(s32 i, s32 max_positive)
+{
+       if(i < max_positive)
+               return i;
+       else
+               return i - 2*max_positive;
+}
+
+// modulo of a negative number does not work consistently in C
+static sqlite3_int64 pythonmodulo(sqlite3_int64 i, sqlite3_int64 mod)
+{
+       if(i >= 0)
+               return i % mod;
+       return mod - ((-i) % mod);
+}
+
+v3s16 ServerMap::getIntegerAsBlock(sqlite3_int64 i)
+{
+       s32 x = unsignedToSigned(pythonmodulo(i, 4096), 2048);
+       i = (i - x) / 4096;
+       s32 y = unsignedToSigned(pythonmodulo(i, 4096), 2048);
+       i = (i - y) / 4096;
+       s32 z = unsignedToSigned(pythonmodulo(i, 4096), 2048);
+       return v3s16(x,y,z);
+}
+
+void ServerMap::listAllLoadableBlocks(core::list<v3s16> &dst)
+{
+       if(loadFromFolders()){
+               errorstream<<"Map::listAllLoadableBlocks(): Result will be missing "
+                               <<"all blocks that are stored in flat files"<<std::endl;
+       }
+       
+       {
+               verifyDatabase();
+               
+               while(sqlite3_step(m_database_list) == SQLITE_ROW)
+               {
+                       sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0);
+                       v3s16 p = getIntegerAsBlock(block_i);
+                       //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
+                       dst.push_back(p);
+               }
+       }
+}
+
 void ServerMap::saveMapMeta()
 {
        DSTACK(__FUNCTION_NAME);
@@ -3036,7 +2978,7 @@ void ServerMap::saveMapMeta()
 
        createDirs(m_savedir);
        
-       std::string fullpath = m_savedir + "/map_meta.txt";
+       std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt";
        std::ofstream os(fullpath.c_str(), std::ios_base::binary);
        if(os.good() == false)
        {
@@ -3062,7 +3004,7 @@ void ServerMap::loadMapMeta()
        infostream<<"ServerMap::loadMapMeta(): Loading map metadata"
                        <<std::endl;
 
-       std::string fullpath = m_savedir + "/map_meta.txt";
+       std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt";
        std::ifstream is(fullpath.c_str(), std::ios_base::binary);
        if(is.good() == false)
        {
@@ -3101,7 +3043,7 @@ void ServerMap::saveSectorMeta(ServerMapSector *sector)
        std::string dir = getSectorDir(pos);
        createDirs(dir);
        
-       std::string fullpath = dir + "/meta";
+       std::string fullpath = dir + DIR_DELIM + "meta";
        std::ofstream o(fullpath.c_str(), std::ios_base::binary);
        if(o.good() == false)
                throw FileNotGoodException("Cannot open sector metafile");
@@ -3119,7 +3061,7 @@ MapSector* ServerMap::loadSectorMeta(std::string sectordir, bool save_after_load
 
        ServerMapSector *sector = NULL;
 
-       std::string fullpath = sectordir + "/meta";
+       std::string fullpath = sectordir + DIR_DELIM + "meta";
        std::ifstream is(fullpath.c_str(), std::ios_base::binary);
        if(is.good() == false)
        {
@@ -3131,7 +3073,7 @@ MapSector* ServerMap::loadSectorMeta(std::string sectordir, bool save_after_load
                                        <<fullpath<<" doesn't exist but directory does."
                                        <<" Continuing with a sector with no metadata."
                                        <<std::endl;*/
-                       sector = new ServerMapSector(this, p2d);
+                       sector = new ServerMapSector(this, p2d, m_gamedef);
                        m_sectors.insert(p2d, sector);
                }
                else
@@ -3142,7 +3084,7 @@ MapSector* ServerMap::loadSectorMeta(std::string sectordir, bool save_after_load
        else
        {
                sector = ServerMapSector::deSerialize
-                               (is, this, p2d, m_sectors);
+                               (is, this, p2d, m_sectors, m_gamedef);
                if(save_after_load)
                        saveSectorMeta(sector);
        }
@@ -3305,7 +3247,7 @@ void ServerMap::saveBlock(MapBlock *block)
 
        createDirs(sectordir);
 
-       std::string fullpath = sectordir+"/"+getBlockFilename(p3d);
+       std::string fullpath = sectordir+DIR_DELIM+getBlockFilename(p3d);
        std::ofstream o(fullpath.c_str(), std::ios_base::binary);
        if(o.good() == false)
                throw FileNotGoodException("Cannot open block data");
@@ -3351,7 +3293,7 @@ void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSecto
 {
        DSTACK(__FUNCTION_NAME);
 
-       std::string fullpath = sectordir+"/"+blockfile;
+       std::string fullpath = sectordir+DIR_DELIM+blockfile;
        try{
 
                std::ifstream is(fullpath.c_str(), std::ios_base::binary);
@@ -3577,7 +3519,7 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos)
        */
 
        std::string blockfilename = getBlockFilename(blockpos);
-       if(fs::PathExists(sectordir+"/"+blockfilename) == false)
+       if(fs::PathExists(sectordir+DIR_DELIM+blockfilename) == false)
                return NULL;
 
        /*
@@ -3600,12 +3542,13 @@ void ServerMap::PrintInfo(std::ostream &out)
 
 ClientMap::ClientMap(
                Client *client,
+               IGameDef *gamedef,
                MapDrawControl &control,
                scene::ISceneNode* parent,
                scene::ISceneManager* mgr,
                s32 id
 ):
-       Map(dout_client),
+       Map(dout_client, gamedef),
        scene::ISceneNode(parent, mgr, id),
        m_client(client),
        m_control(control),
@@ -3643,7 +3586,7 @@ MapSector * ClientMap::emergeSector(v2s16 p2d)
        }
        
        // Create a sector
-       ClientMapSector *sector = new ClientMapSector(this, p2d);
+       ClientMapSector *sector = new ClientMapSector(this, p2d, m_gamedef);
        
        {
                //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out
@@ -3692,13 +3635,50 @@ void ClientMap::OnRegisterSceneNode()
        ISceneNode::OnRegisterSceneNode();
 }
 
+static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac,
+               float start_off, float end_off, u32 needed_count, INodeDefManager *nodemgr)
+{
+       float d0 = (float)BS * p0.getDistanceFrom(p1);
+       v3s16 u0 = p1 - p0;
+       v3f uf = v3f(u0.X, u0.Y, u0.Z) * BS;
+       uf.normalize();
+       v3f p0f = v3f(p0.X, p0.Y, p0.Z) * BS;
+       u32 count = 0;
+       for(float s=start_off; s<d0+end_off; s+=step){
+               v3f pf = p0f + uf * s;
+               v3s16 p = floatToInt(pf, BS);
+               MapNode n = map->getNodeNoEx(p);
+               bool is_transparent = false;
+               const ContentFeatures &f = nodemgr->get(n);
+               if(f.solidness == 0)
+                       is_transparent = (f.visual_solidness != 2);
+               else
+                       is_transparent = (f.solidness != 2);
+               if(!is_transparent){
+                       count++;
+                       if(count >= needed_count)
+                               return true;
+               }
+               step *= stepfac;
+       }
+       return false;
+}
+
 void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        //m_dout<<DTIME<<"Rendering map..."<<std::endl;
        DSTACK(__FUNCTION_NAME);
 
        bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
        
+       std::string prefix;
+       if(pass == scene::ESNRP_SOLID)
+               prefix = "CM: solid: ";
+       else
+               prefix = "CM: transparent: ";
+
        /*
                This is called two times per frame, reset on the non-transparent one
        */
@@ -3727,55 +3707,59 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                Get all blocks and draw all visible ones
        */
 
-       v3s16 cam_pos_nodes(
-                       camera_position.X / BS,
-                       camera_position.Y / BS,
-                       camera_position.Z / BS);
-
+       v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+       
        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;
 
        // Take a fair amount as we will be dropping more out later
+       // Umm... these additions are a bit strange but they are needed.
        v3s16 p_blocks_min(
-                       p_nodes_min.X / MAP_BLOCKSIZE - 2,
-                       p_nodes_min.Y / MAP_BLOCKSIZE - 2,
-                       p_nodes_min.Z / MAP_BLOCKSIZE - 2);
+                       p_nodes_min.X / MAP_BLOCKSIZE - 3,
+                       p_nodes_min.Y / MAP_BLOCKSIZE - 3,
+                       p_nodes_min.Z / MAP_BLOCKSIZE - 3);
        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);
        
        u32 vertex_count = 0;
+       u32 meshbuffer_count = 0;
        
        // For limiting number of mesh updates per frame
        u32 mesh_update_count = 0;
        
+       // Number of blocks in rendering range
+       u32 blocks_in_range = 0;
+       // Number of blocks occlusion culled
+       u32 blocks_occlusion_culled = 0;
+       // Number of blocks in rendering range but don't have a mesh
+       u32 blocks_in_range_without_mesh = 0;
+       // Blocks that had mesh that would have been drawn according to
+       // rendering range (if max blocks limit didn't kick in)
        u32 blocks_would_have_drawn = 0;
+       // Blocks that were drawn and had a mesh
        u32 blocks_drawn = 0;
+       // Blocks which had a corresponding meshbuffer for this pass
+       u32 blocks_had_pass_meshbuf = 0;
+       // Blocks from which stuff was actually drawn
+       u32 blocks_without_stuff = 0;
+
+       /*
+               Collect a set of blocks for drawing
+       */
+       
+       core::map<v3s16, MapBlock*> drawset;
 
-       int timecheck_counter = 0;
-       core::map<v2s16, MapSector*>::Iterator si;
-       si = m_sectors.getIterator();
-       for(; si.atEnd() == false; si++)
        {
-               {
-                       timecheck_counter++;
-                       if(timecheck_counter > 50)
-                       {
-                               timecheck_counter = 0;
-                               int time2 = time(0);
-                               if(time2 > time1 + 4)
-                               {
-                                       infostream<<"ClientMap::renderMap(): "
-                                               "Rendering takes ages, returning."
-                                               <<std::endl;
-                                       return;
-                               }
-                       }
-               }
+       ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG);
 
+       for(core::map<v2s16, MapSector*>::Iterator
+                       si = m_sectors.getIterator();
+                       si.atEnd() == false; si++)
+       {
                MapSector *sector = si.getNode()->getValue();
                v2s16 sp = sector->getPos();
                
@@ -3792,11 +3776,11 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                sector->getBlocks(sectorblocks);
                
                /*
-                       Draw blocks
+                       Loop through blocks in sector
                */
-               
-               u32 sector_blocks_drawn = 0;
 
+               u32 sector_blocks_drawn = 0;
+               
                core::list< MapBlock * >::Iterator i;
                for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
                {
@@ -3810,7 +3794,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                        float range = 100000 * BS;
                        if(m_control.range_all == false)
                                range = m_control.wanted_range * BS;
-                       
+
                        float d = 0.0;
                        if(isBlockInSight(block->getPos(), camera_position,
                                        camera_direction, camera_fov,
@@ -3819,14 +3803,13 @@ 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 &&
                                        d - 0.5*BS*MAP_BLOCKSIZE > range)
                                continue;*/
 
+                       blocks_in_range++;
+                       
 #if 1
                        /*
                                Update expired mesh (used for day/night change)
@@ -3844,8 +3827,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
                                // Mesh has not been expired and there is no mesh:
                                // block has no content
-                               if(block->mesh == NULL && mesh_expired == false)
+                               if(block->mesh == NULL && mesh_expired == false){
+                                       blocks_in_range_without_mesh++;
                                        continue;
+                               }
                        }
 
                        f32 faraway = BS*50;
@@ -3874,59 +3859,175 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
                                mesh_expired = false;
                        }
-                       
 #endif
+
+                       /*
+                               Occlusion culling
+                       */
+
+                       v3s16 cpn = block->getPos() * MAP_BLOCKSIZE;
+                       cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2);
+                       float step = BS*1;
+                       float stepfac = 1.1;
+                       float startoff = BS*1;
+                       float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42;
+                       v3s16 spn = cam_pos_nodes + v3s16(0,0,0);
+                       s16 bs2 = MAP_BLOCKSIZE/2 + 1;
+                       u32 needed_count = 1;
+                       if(
+                               isOccluded(this, spn, cpn + v3s16(0,0,0),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr) &&
+                               isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2),
+                                       step, stepfac, startoff, endoff, needed_count, nodemgr)
+                       )
+                       {
+                               blocks_occlusion_culled++;
+                               continue;
+                       }
+                       
+                       // This block is in range. Reset usage timer.
+                       block->resetUsageTimer();
+
                        /*
-                               Draw the faces of the block
+                               Ignore if mesh doesn't exist
                        */
                        {
                                JMutexAutoLock lock(block->mesh_mutex);
 
                                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)
+                               if(mesh == NULL){
+                                       blocks_in_range_without_mesh++;
                                        continue;
+                               }
+                       }
+                       
+                       // Limit block count in case of a sudden increase
+                       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;
+                       
+                       // Add to set
+                       drawset[block->getPos()] = block;
+                       
+                       sector_blocks_drawn++;
+                       blocks_drawn++;
 
-                               blocks_drawn++;
-                               sector_blocks_drawn++;
+               } // foreach sectorblocks
 
-                               u32 c = mesh->getMeshBufferCount();
+               if(sector_blocks_drawn != 0)
+                       m_last_drawn_sectors[sp] = true;
+       }
+       } // ScopeProfiler
+       
+       /*
+               Draw the selected MapBlocks
+       */
 
-                               for(u32 i=0; i<c; i++)
+       {
+       ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG);
+
+       int timecheck_counter = 0;
+       for(core::map<v3s16, MapBlock*>::Iterator
+                       i = drawset.getIterator();
+                       i.atEnd() == false; i++)
+       {
+               {
+                       timecheck_counter++;
+                       if(timecheck_counter > 50)
+                       {
+                               timecheck_counter = 0;
+                               int time2 = time(0);
+                               if(time2 > time1 + 4)
                                {
-                                       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)
-                                       {
-                                               /*
-                                                       This *shouldn't* hurt too much because Irrlicht
-                                                       doesn't change opengl textures if the old
-                                                       material is set again.
-                                               */
-                                               driver->setMaterial(buf->getMaterial());
-                                               driver->drawMeshBuffer(buf);
-                                               vertex_count += buf->getVertexCount();
-                                       }
+                                       infostream<<"ClientMap::renderMap(): "
+                                               "Rendering takes ages, returning."
+                                               <<std::endl;
+                                       return;
                                }
                        }
-               } // foreach sectorblocks
+               }
+               
+               MapBlock *block = i.getNode()->getValue();
 
-               if(sector_blocks_drawn != 0)
+               /*
+                       Draw the faces of the block
+               */
                {
-                       m_last_drawn_sectors[sp] = true;
+                       JMutexAutoLock lock(block->mesh_mutex);
+
+                       scene::SMesh *mesh = block->mesh;
+                       assert(mesh);
+                       
+                       u32 c = mesh->getMeshBufferCount();
+                       bool stuff_actually_drawn = false;
+                       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)
+                               {
+                                       if(buf->getVertexCount() == 0)
+                                               errorstream<<"Block ["<<analyze_block(block)
+                                                               <<"] contains an empty meshbuf"<<std::endl;
+                                       /*
+                                               This *shouldn't* hurt too much because Irrlicht
+                                               doesn't change opengl textures if the old
+                                               material has the same texture.
+                                       */
+                                       driver->setMaterial(buf->getMaterial());
+                                       driver->drawMeshBuffer(buf);
+                                       vertex_count += buf->getVertexCount();
+                                       meshbuffer_count++;
+                                       stuff_actually_drawn = true;
+                               }
+                       }
+                       if(stuff_actually_drawn)
+                               blocks_had_pass_meshbuf++;
+                       else
+                               blocks_without_stuff++;
                }
        }
+       } // ScopeProfiler
        
+       // Log only on solid pass because values are the same
+       if(pass == scene::ESNRP_SOLID){
+               g_profiler->avg("CM: blocks in range", blocks_in_range);
+               g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
+               if(blocks_in_range != 0)
+                       g_profiler->avg("CM: blocks in range without mesh (frac)",
+                                       (float)blocks_in_range_without_mesh/blocks_in_range);
+               g_profiler->avg("CM: blocks drawn", blocks_drawn);
+       }
+       
+       g_profiler->avg(prefix+"vertices drawn", vertex_count);
+       if(blocks_had_pass_meshbuf != 0)
+               g_profiler->avg(prefix+"meshbuffers per block",
+                               (float)meshbuffer_count / (float)blocks_had_pass_meshbuf);
+       if(blocks_drawn != 0)
+               g_profiler->avg(prefix+"empty blocks (frac)",
+                               (float)blocks_without_stuff / blocks_drawn);
+
        m_control.blocks_drawn = blocks_drawn;
        m_control.blocks_would_have_drawn = blocks_would_have_drawn;
 
@@ -3936,6 +4037,8 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
 void ClientMap::renderPostFx()
 {
+       INodeDefManager *nodemgr = m_gamedef->ndef();
+
        // Sadly ISceneManager has no "post effects" render pass, in that case we
        // could just register for that and handle it in renderMap().
 
@@ -3947,7 +4050,7 @@ void ClientMap::renderPostFx()
 
        // - If the player is in a solid node, make everything black.
        // - If the player is in liquid, draw a semi-transparent overlay.
-       ContentFeatures& features = content_features(n);
+       const ContentFeatures& features = nodemgr->get(n);
        video::SColor post_effect_color = features.post_effect_color;
        if(features.solidness == 2 && g_settings->getBool("free_move") == false)
        {