]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/mapblock.cpp
Don't include client/game.h on server build
[dragonfireclient.git] / src / mapblock.cpp
index 016cb222e0325c0fb86e7abf9028efbc7ab65801..e3a6caa199616f6e416e1dc2c044dea12e1a0c17 100644 (file)
@@ -1,18 +1,18 @@
 /*
-Minetest-c55
-Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 
 This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+GNU Lesser General Public License for more details.
 
-You should have received a copy of the GNU General Public License along
+You should have received a copy of the GNU Lesser General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
@@ -21,8 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include <sstream>
 #include "map.h"
-// For g_settings
-#include "main.h"
 #include "light.h"
 #include "nodedef.h"
 #include "nodemetadata.h"
@@ -30,379 +28,109 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 #include "nameidmapping.h"
 #include "content_mapnode.h" // For legacy name-id mapping
+#include "content_nodemeta.h" // For legacy deserialization
+#include "serialization.h"
+#ifndef SERVER
+#include "client/mapblock_mesh.h"
+#endif
+#include "porting.h"
+#include "util/string.h"
+#include "util/serialize.h"
+#include "util/basic_macros.h"
+
+static const char *modified_reason_strings[] = {
+       "initial",
+       "reallocate",
+       "setIsUnderground",
+       "setLightingExpired",
+       "setGenerated",
+       "setNode",
+       "setNodeNoCheck",
+       "setTimestamp",
+       "NodeMetaRef::reportMetadataChange",
+       "clearAllObjects",
+       "Timestamp expired (step)",
+       "addActiveObjectRaw",
+       "removeRemovedObjects/remove",
+       "removeRemovedObjects/deactivate",
+       "Stored list cleared in activateObjects due to overflow",
+       "deactivateFarObjects: Static data moved in",
+       "deactivateFarObjects: Static data moved out",
+       "deactivateFarObjects: Static data changed considerably",
+       "finishBlockMake: expireDayNightDiff",
+       "unknown",
+};
+
 
 /*
        MapBlock
 */
 
 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
-               m_node_metadata(new NodeMetadataList),
                m_parent(parent),
                m_pos(pos),
-               m_gamedef(gamedef),
-               m_modified(MOD_STATE_WRITE_NEEDED),
-               m_modified_reason("initial"),
-               m_modified_reason_too_long(false),
-               is_underground(false),
-               m_lighting_expired(true),
-               m_day_night_differs(false),
-               m_generated(false),
-               m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
-               m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
-               m_usage_timer(0)
+               m_pos_relative(pos * MAP_BLOCKSIZE),
+               m_gamedef(gamedef)
 {
-       data = NULL;
-       if(dummy == false)
+       if (!dummy)
                reallocate();
-       
-#ifndef SERVER
-       m_mesh_expired = false;
-       mesh_mutex.Init();
-       mesh = NULL;
-       m_temp_mods_mutex.Init();
-#endif
 }
 
 MapBlock::~MapBlock()
 {
 #ifndef SERVER
        {
-               JMutexAutoLock lock(mesh_mutex);
-               
-               if(mesh)
-               {
-                       mesh->drop();
-                       mesh = NULL;
-               }
+               delete mesh;
+               mesh = nullptr;
        }
 #endif
 
-       delete m_node_metadata;
-
-       if(data)
-               delete[] data;
+       delete[] data;
 }
 
 bool MapBlock::isValidPositionParent(v3s16 p)
 {
-       if(isValidPosition(p))
-       {
+       if (isValidPosition(p)) {
                return true;
        }
-       else{
-               return m_parent->isValidPosition(getPosRelative() + p);
-       }
-}
 
-MapNode MapBlock::getNodeParent(v3s16 p)
-{
-       if(isValidPosition(p) == false)
-       {
-               return m_parent->getNode(getPosRelative() + p);
-       }
-       else
-       {
-               if(data == NULL)
-                       throw InvalidPositionException();
-               return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
-       }
+       return m_parent->isValidPosition(getPosRelative() + p);
 }
 
-void MapBlock::setNodeParent(v3s16 p, MapNode & n)
+MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
 {
-       if(isValidPosition(p) == false)
-       {
-               m_parent->setNode(getPosRelative() + p, n);
-       }
-       else
-       {
-               if(data == NULL)
-                       throw InvalidPositionException();
-               data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
-       }
-}
+       if (!isValidPosition(p))
+               return m_parent->getNode(getPosRelative() + p, is_valid_position);
 
-MapNode MapBlock::getNodeParentNoEx(v3s16 p)
-{
-       if(isValidPosition(p) == false)
-       {
-               try{
-                       return m_parent->getNode(getPosRelative() + p);
-               }
-               catch(InvalidPositionException &e)
-               {
-                       return MapNode(CONTENT_IGNORE);
-               }
-       }
-       else
-       {
-               if(data == NULL)
-               {
-                       return MapNode(CONTENT_IGNORE);
-               }
-               return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
+       if (!data) {
+               if (is_valid_position)
+                       *is_valid_position = false;
+               return {CONTENT_IGNORE};
        }
+       if (is_valid_position)
+               *is_valid_position = true;
+       return data[p.Z * zstride + p.Y * ystride + p.X];
 }
 
-#ifndef SERVER
-
-#if 1
-void MapBlock::updateMesh(u32 daynight_ratio)
+std::string MapBlock::getModifiedReasonString()
 {
-#if 0
-       /*
-               DEBUG: If mesh has been generated, don't generate it again
-       */
-       {
-               JMutexAutoLock meshlock(mesh_mutex);
-               if(mesh != NULL)
-                       return;
-       }
-#endif
+       std::string reason;
 
-       MeshMakeData data;
-       data.fill(daynight_ratio, this);
-       
-       scene::SMesh *mesh_new = makeMapBlockMesh(&data, m_gamedef);
-       
-       /*
-               Replace the mesh
-       */
+       const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
+               ARRLEN(modified_reason_strings));
 
-       replaceMesh(mesh_new);
-
-}
-#endif
-
-void MapBlock::replaceMesh(scene::SMesh *mesh_new)
-{
-       mesh_mutex.Lock();
-
-       //scene::SMesh *mesh_old = mesh[daynight_i];
-       //mesh[daynight_i] = mesh_new;
-
-       scene::SMesh *mesh_old = mesh;
-       mesh = mesh_new;
-       setMeshExpired(false);
-       
-       if(mesh_old != NULL)
-       {
-               // Remove hardware buffers of meshbuffers of mesh
-               // NOTE: No way, this runs in a different thread and everything
-               /*u32 c = mesh_old->getMeshBufferCount();
-               for(u32 i=0; i<c; i++)
-               {
-                       IMeshBuffer *buf = mesh_old->getMeshBuffer(i);
-               }*/
-               
-               /*infostream<<"mesh_old->getReferenceCount()="
-                               <<mesh_old->getReferenceCount()<<std::endl;
-               u32 c = mesh_old->getMeshBufferCount();
-               for(u32 i=0; i<c; i++)
-               {
-                       scene::IMeshBuffer *buf = mesh_old->getMeshBuffer(i);
-                       infostream<<"buf->getReferenceCount()="
-                                       <<buf->getReferenceCount()<<std::endl;
-               }*/
-
-               // Drop the mesh
-               mesh_old->drop();
+       for (u32 i = 0; i != ubound; i++) {
+               if ((m_modified_reason & (1 << i)) == 0)
+                       continue;
 
-               //delete mesh_old;
+               reason += modified_reason_strings[i];
+               reason += ", ";
        }
 
-       mesh_mutex.Unlock();
-}
-       
-#endif // !SERVER
-
-/*
-       Propagates sunlight down through the block.
-       Doesn't modify nodes that are not affected by sunlight.
-       
-       Returns false if sunlight at bottom block is invalid.
-       Returns true if sunlight at bottom block is valid.
-       Returns true if bottom block doesn't exist.
-
-       If there is a block above, continues from it.
-       If there is no block above, assumes there is sunlight, unless
-       is_underground is set or highest node is water.
-
-       All sunlighted nodes are added to light_sources.
-
-       if remove_light==true, sets non-sunlighted nodes black.
-
-       if black_air_left!=NULL, it is set to true if non-sunlighted
-       air is left in block.
-*/
-bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources,
-               bool remove_light, bool *black_air_left)
-{
-       INodeDefManager *nodemgr = m_gamedef->ndef();
-
-       // Whether the sunlight at the top of the bottom block is valid
-       bool block_below_is_valid = true;
-       
-       v3s16 pos_relative = getPosRelative();
-       
-       for(s16 x=0; x<MAP_BLOCKSIZE; x++)
-       {
-               for(s16 z=0; z<MAP_BLOCKSIZE; z++)
-               {
-#if 1
-                       bool no_sunlight = false;
-                       bool no_top_block = false;
-                       // Check if node above block has sunlight
-                       try{
-                               MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
-                               if(n.getContent() == CONTENT_IGNORE)
-                               {
-                                       // Trust heuristics
-                                       no_sunlight = is_underground;
-                               }
-                               else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
-                               {
-                                       no_sunlight = true;
-                               }
-                       }
-                       catch(InvalidPositionException &e)
-                       {
-                               no_top_block = true;
-                               
-                               // NOTE: This makes over-ground roofed places sunlighted
-                               // Assume sunlight, unless is_underground==true
-                               if(is_underground)
-                               {
-                                       no_sunlight = true;
-                               }
-                               else
-                               {
-                                       MapNode n = getNode(v3s16(x, MAP_BLOCKSIZE-1, z));
-                                       if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
-                                       {
-                                               no_sunlight = true;
-                                       }
-                               }
-                               // NOTE: As of now, this just would make everything dark.
-                               // No sunlight here
-                               //no_sunlight = true;
-                       }
-#endif
-#if 0 // Doesn't work; nothing gets light.
-                       bool no_sunlight = true;
-                       bool no_top_block = false;
-                       // Check if node above block has sunlight
-                       try{
-                               MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
-                               if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
-                               {
-                                       no_sunlight = false;
-                               }
-                       }
-                       catch(InvalidPositionException &e)
-                       {
-                               no_top_block = true;
-                       }
-#endif
-
-                       /*std::cout<<"("<<x<<","<<z<<"): "
-                                       <<"no_top_block="<<no_top_block
-                                       <<", is_underground="<<is_underground
-                                       <<", no_sunlight="<<no_sunlight
-                                       <<std::endl;*/
-               
-                       s16 y = MAP_BLOCKSIZE-1;
-                       
-                       // This makes difference to diminishing in water.
-                       bool stopped_to_solid_object = false;
-                       
-                       u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
-
-                       for(; y >= 0; y--)
-                       {
-                               v3s16 pos(x, y, z);
-                               MapNode &n = getNodeRef(pos);
-                               
-                               if(current_light == 0)
-                               {
-                                       // Do nothing
-                               }
-                               else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
-                               {
-                                       // Do nothing: Sunlight is continued
-                               }
-                               else if(nodemgr->get(n).light_propagates == false)
-                               {
-                                       // A solid object is on the way.
-                                       stopped_to_solid_object = true;
-                                       
-                                       // Light stops.
-                                       current_light = 0;
-                               }
-                               else
-                               {
-                                       // Diminish light
-                                       current_light = diminish_light(current_light);
-                               }
-
-                               u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
-
-                               if(current_light > old_light || remove_light)
-                               {
-                                       n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
-                               }
-                               
-                               if(diminish_light(current_light) != 0)
-                               {
-                                       light_sources.insert(pos_relative + pos, true);
-                               }
-
-                               if(current_light == 0 && stopped_to_solid_object)
-                               {
-                                       if(black_air_left)
-                                       {
-                                               *black_air_left = true;
-                                       }
-                               }
-                       }
-
-                       // Whether or not the block below should see LIGHT_SUN
-                       bool sunlight_should_go_down = (current_light == LIGHT_SUN);
-
-                       /*
-                               If the block below hasn't already been marked invalid:
-
-                               Check if the node below the block has proper sunlight at top.
-                               If not, the block below is invalid.
-                               
-                               Ignore non-transparent nodes as they always have no light
-                       */
-                       try
-                       {
-                       if(block_below_is_valid)
-                       {
-                               MapNode n = getNodeParent(v3s16(x, -1, z));
-                               if(nodemgr->get(n).light_propagates)
-                               {
-                                       if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
-                                                       && sunlight_should_go_down == false)
-                                               block_below_is_valid = false;
-                                       else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
-                                                       && sunlight_should_go_down == true)
-                                               block_below_is_valid = false;
-                               }
-                       }//if
-                       }//try
-                       catch(InvalidPositionException &e)
-                       {
-                               /*std::cout<<"InvalidBlockException for bottom block node"
-                                               <<std::endl;*/
-                               // Just no block below, no need to panic.
-                       }
-               }
-       }
+       if (reason.length() > 2)
+               reason.resize(reason.length() - 2);
 
-       return block_below_is_valid;
+       return reason;
 }
 
 
@@ -410,7 +138,7 @@ void MapBlock::copyTo(VoxelManipulator &dst)
 {
        v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
        VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
-       
+
        // Copy from data to VoxelManipulator
        dst.copyFrom(data, data_area, v3s16(0,0,0),
                        getPosRelative(), data_size);
@@ -420,18 +148,20 @@ void MapBlock::copyFrom(VoxelManipulator &dst)
 {
        v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
        VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
-       
+
        // Copy from VoxelManipulator to data
        dst.copyTo(data, data_area, v3s16(0,0,0),
                        getPosRelative(), data_size);
 }
 
-void MapBlock::updateDayNightDiff()
+void MapBlock::actuallyUpdateDayNightDiff()
 {
-       INodeDefManager *nodemgr = m_gamedef->ndef();
+       const NodeDefManager *nodemgr = m_gamedef->ndef();
 
-       if(data == NULL)
-       {
+       // Running this function un-expires m_day_night_differs
+       m_day_night_differs_expired = false;
+
+       if (!data) {
                m_day_night_differs = false;
                return;
        }
@@ -441,33 +171,35 @@ void MapBlock::updateDayNightDiff()
        /*
                Check if any lighting value differs
        */
-       for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
-       {
-               MapNode &n = data[i];
-               if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr))
-               {
-                       differs = true;
+
+       MapNode previous_n(CONTENT_IGNORE);
+       for (u32 i = 0; i < nodecount; i++) {
+               MapNode n = data[i];
+
+               // If node is identical to previous node, don't verify if it differs
+               if (n == previous_n)
+                       continue;
+
+               differs = !n.isLightDayNightEq(nodemgr);
+               if (differs)
                        break;
-               }
+               previous_n = n;
        }
 
        /*
                If some lighting values differ, check if the whole thing is
-               just air. If it is, differ = false
+               just air. If it is just air, differs = false
        */
-       if(differs)
-       {
+       if (differs) {
                bool only_air = true;
-               for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
-               {
+               for (u32 i = 0; i < nodecount; i++) {
                        MapNode &n = data[i];
-                       if(n.getContent() != CONTENT_AIR)
-                       {
+                       if (n.getContent() != CONTENT_AIR) {
                                only_air = false;
                                break;
                        }
                }
-               if(only_air)
+               if (only_air)
                        differs = false;
        }
 
@@ -475,302 +207,433 @@ void MapBlock::updateDayNightDiff()
        m_day_night_differs = differs;
 }
 
-s16 MapBlock::getGroundLevel(v2s16 p2d)
+void MapBlock::expireDayNightDiff()
 {
-       if(isDummy())
-               return -3;
-       try
-       {
-               s16 y = MAP_BLOCKSIZE-1;
-               for(; y>=0; y--)
-               {
-                       MapNode n = getNodeRef(p2d.X, y, p2d.Y);
-                       if(m_gamedef->ndef()->get(n).walkable)
-                       {
-                               if(y == MAP_BLOCKSIZE-1)
-                                       return -2;
-                               else
-                                       return y;
-                       }
-               }
-               return -1;
-       }
-       catch(InvalidPositionException &e)
-       {
-               return -3;
+       if (!data) {
+               m_day_night_differs = false;
+               m_day_night_differs_expired = false;
+               return;
        }
+
+       m_day_night_differs_expired = true;
 }
 
 /*
        Serialization
 */
-
 // List relevant id-name pairs for ids in the block using nodedef
-static void getBlockNodeIdMapping(NameIdMapping *nimap, MapBlock *block,
-               INodeDefManager *nodedef)
+// Renumbers the content IDs (starting at 0 and incrementing
+// use static memory requires about 65535 * sizeof(int) ram in order to be
+// sure we can handle all content ids. But it's absolutely worth it as it's
+// a speedup of 4 for one of the major time consuming functions on storing
+// mapblocks.
+static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
+static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
+       const NodeDefManager *nodedef)
 {
+       memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
+
        std::set<content_t> unknown_contents;
-       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);
-               content_t id = n.getContent();
-               const ContentFeatures &f = nodedef->get(id);
-               const std::string &name = f.name;
-               if(name == "")
-                       unknown_contents.insert(id);
+       content_t id_counter = 0;
+       for (u32 i = 0; i < MapBlock::nodecount; i++) {
+               content_t global_id = nodes[i].getContent();
+               content_t id = CONTENT_IGNORE;
+
+               // Try to find an existing mapping
+               if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
+                       id = getBlockNodeIdMapping_mapping[global_id];
+               }
                else
-                       nimap->set(id, name);
+               {
+                       // We have to assign a new mapping
+                       id = id_counter++;
+                       getBlockNodeIdMapping_mapping[global_id] = id;
+
+                       const ContentFeatures &f = nodedef->get(global_id);
+                       const std::string &name = f.name;
+                       if (name.empty())
+                               unknown_contents.insert(global_id);
+                       else
+                               nimap->set(id, name);
+               }
+
+               // Update the MapNode
+               nodes[i].setContent(id);
        }
-       for(std::set<content_t>::const_iterator
-                       i = unknown_contents.begin();
-                       i != unknown_contents.end(); i++){
-               errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
-                               <<"Name for node id "<<(*i)<<" not known"<<std::endl;
+       for (u16 unknown_content : unknown_contents) {
+               errorstream << "getBlockNodeIdMapping(): IGNORING ERROR: "
+                               << "Name for node id " << unknown_content << " not known" << std::endl;
        }
 }
 // Correct ids in the block to match nodedef based on names.
 // Unknown ones are added to nodedef.
 // Will not update itself to match id-name pairs in nodedef.
-void correctBlockNodeIds(const NameIdMapping *nimap, MapBlock *block,
+static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
                IGameDef *gamedef)
 {
-       INodeDefManager *nodedef = gamedef->ndef();
+       const NodeDefManager *nodedef = gamedef->ndef();
        // This means the block contains incorrect ids, and we contain
        // the information to convert those to names.
        // nodedef contains information to convert our names to globally
        // correct ids.
-       std::set<content_t> unnamed_contents;
-       std::set<std::string> unallocatable_contents;
-       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);
-               content_t local_id = n.getContent();
+       std::unordered_set<content_t> unnamed_contents;
+       std::unordered_set<std::string> unallocatable_contents;
+
+       bool previous_exists = false;
+       content_t previous_local_id = CONTENT_IGNORE;
+       content_t previous_global_id = CONTENT_IGNORE;
+
+       for (u32 i = 0; i < MapBlock::nodecount; i++) {
+               content_t local_id = nodes[i].getContent();
+               // If previous node local_id was found and same than before, don't lookup maps
+               // apply directly previous resolved id
+               // This permits to massively improve loading performance when nodes are similar
+               // example: default:air, default:stone are massively present
+               if (previous_exists && local_id == previous_local_id) {
+                       nodes[i].setContent(previous_global_id);
+                       continue;
+               }
+
                std::string name;
-               bool found = nimap->getName(local_id, name);
-               if(!found){
+               if (!nimap->getName(local_id, name)) {
                        unnamed_contents.insert(local_id);
+                       previous_exists = false;
                        continue;
                }
+
                content_t global_id;
-               found = nodedef->getId(name, global_id);
-               if(!found){
+               if (!nodedef->getId(name, global_id)) {
                        global_id = gamedef->allocateUnknownNodeId(name);
-                       if(global_id == CONTENT_IGNORE){
+                       if (global_id == CONTENT_IGNORE) {
                                unallocatable_contents.insert(name);
+                               previous_exists = false;
                                continue;
                        }
                }
-               n.setContent(global_id);
-               block->setNode(p, n);
+               nodes[i].setContent(global_id);
+
+               // Save previous node local_id & global_id result
+               previous_local_id = local_id;
+               previous_global_id = global_id;
+               previous_exists = true;
        }
-       for(std::set<content_t>::const_iterator
-                       i = unnamed_contents.begin();
-                       i != unnamed_contents.end(); i++){
-               errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
-                               <<"Block contains id "<<(*i)
-                               <<" with no name mapping"<<std::endl;
+
+       for (const content_t c: unnamed_contents) {
+               errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
+                               << "Block contains id " << c
+                               << " with no name mapping" << std::endl;
        }
-       for(std::set<std::string>::const_iterator
-                       i = unallocatable_contents.begin();
-                       i != unallocatable_contents.end(); i++){
-               errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
-                               <<"Could not allocate global id for node name \""
-                               <<(*i)<<"\""<<std::endl;
+       for (const std::string &node_name: unallocatable_contents) {
+               errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
+                               << "Could not allocate global id for node name \""
+                               << node_name << "\"" << std::endl;
        }
 }
 
-void MapBlock::serialize(std::ostream &os, u8 version)
+void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level)
 {
        if(!ser_ver_supported(version))
                throw VersionMismatchException("ERROR: MapBlock format not supported");
-       
-       if(data == NULL)
-       {
+
+       if (!data)
                throw SerializationError("ERROR: Not writing dummy block.");
-       }
-       
-       // These have no compression
-       if(version <= 3 || version == 5 || version == 6)
-       {
-               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
-               
-               u32 buflen = 1 + nodecount * MapNode::serializedLength(version);
-               SharedBuffer<u8> dest(buflen);
 
-               dest[0] = is_underground;
-               for(u32 i=0; i<nodecount; i++)
-               {
-                       u32 s = 1 + i * MapNode::serializedLength(version);
-                       data[i].serialize(&dest[s], version);
-               }
-               
-               os.write((char*)*dest, dest.getSize());
+       FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
+
+       std::ostringstream os_raw(std::ios_base::binary);
+       std::ostream &os = version >= 29 ? os_raw : os_compressed;
+
+       // First byte
+       u8 flags = 0;
+       if(is_underground)
+               flags |= 0x01;
+       if(getDayNightDiff())
+               flags |= 0x02;
+       if (!m_generated)
+               flags |= 0x08;
+       writeU8(os, flags);
+       if (version >= 27) {
+               writeU16(os, m_lighting_complete);
        }
-       else if(version <= 10)
+
+       /*
+               Bulk node data
+       */
+       NameIdMapping nimap;
+       SharedBuffer<u8> buf;
+       const u8 content_width = 2;
+       const u8 params_width = 2;
+       if(disk)
        {
-               /*
-                       With compression.
-                       Compress the materials and the params separately.
-               */
-               
-               // First byte
-               os.write((char*)&is_underground, 1);
+               MapNode *tmp_nodes = new MapNode[nodecount];
+               memcpy(tmp_nodes, data, nodecount * sizeof(MapNode));
+               getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
 
-               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+               buf = MapNode::serializeBulk(version, tmp_nodes, nodecount,
+                               content_width, params_width);
+               delete[] tmp_nodes;
 
-               // Get and compress materials
-               SharedBuffer<u8> materialdata(nodecount);
-               for(u32 i=0; i<nodecount; i++)
-               {
-                       materialdata[i] = data[i].param0;
-               }
-               compress(materialdata, os, version);
+               // write timestamp and node/id mapping first
+               if (version >= 29) {
+                       writeU32(os, getTimestamp());
 
-               // Get and compress lights
-               SharedBuffer<u8> lightdata(nodecount);
-               for(u32 i=0; i<nodecount; i++)
-               {
-                       lightdata[i] = data[i].param1;
-               }
-               compress(lightdata, os, version);
-               
-               if(version >= 10)
-               {
-                       // Get and compress param2
-                       SharedBuffer<u8> param2data(nodecount);
-                       for(u32 i=0; i<nodecount; i++)
-                       {
-                               param2data[i] = data[i].param2;
-                       }
-                       compress(param2data, os, version);
+                       nimap.serialize(os);
                }
        }
-       // All other versions (newest)
        else
        {
-               // First byte
-               u8 flags = 0;
-               if(is_underground)
-                       flags |= 0x01;
-               if(m_day_night_differs)
-                       flags |= 0x02;
-               if(m_lighting_expired)
-                       flags |= 0x04;
-               if(version >= 18)
-               {
-                       if(m_generated == false)
-                               flags |= 0x08;
+               buf = MapNode::serializeBulk(version, data, nodecount,
+                               content_width, params_width);
+       }
+
+       writeU8(os, content_width);
+       writeU8(os, params_width);
+       if (version >= 29) {
+               os.write(reinterpret_cast<char*>(*buf), buf.getSize());
+       } else {
+               // prior to 29 node data was compressed individually
+               compress(buf, os, version, compression_level);
+       }
+
+       /*
+               Node metadata
+       */
+       if (version >= 29) {
+               m_node_metadata.serialize(os, version, disk);
+       } else {
+               // use os_raw from above to avoid allocating another stream object
+               m_node_metadata.serialize(os_raw, version, disk);
+               // prior to 29 node data was compressed individually
+               compress(os_raw.str(), os, version, compression_level);
+       }
+
+       /*
+               Data that goes to disk, but not the network
+       */
+       if(disk)
+       {
+               if(version <= 24){
+                       // Node timers
+                       m_node_timers.serialize(os, version);
                }
-               os.write((char*)&flags, 1);
-               
-               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
 
-               /*
-                       Get data
-               */
+               // Static objects
+               m_static_objects.serialize(os);
 
-               // Serialize nodes
-               SharedBuffer<u8> databuf_nodelist(nodecount*3);
-               for(u32 i=0; i<nodecount; i++)
-               {
-                       data[i].serialize(&databuf_nodelist[i*3], version);
+               if(version < 29){
+                       // Timestamp
+                       writeU32(os, getTimestamp());
+
+                       // Write block-specific node definition id mapping
+                       nimap.serialize(os);
                }
-               
-               // Create buffer with different parameters sorted
-               SharedBuffer<u8> databuf(nodecount*3);
-               for(u32 i=0; i<nodecount; i++)
-               {
-                       databuf[i] = databuf_nodelist[i*3];
-                       databuf[i+nodecount] = databuf_nodelist[i*3+1];
-                       databuf[i+nodecount*2] = databuf_nodelist[i*3+2];
+
+               if(version >= 25){
+                       // Node timers
+                       m_node_timers.serialize(os, version);
                }
+       }
 
-               /*
-                       Compress data to output stream
-               */
+       if (version >= 29) {
+               // now compress the whole thing
+               compress(os_raw.str(), os_compressed, version, compression_level);
+       }
+}
 
-               compress(databuf, os, version);
-               
-               /*
-                       NodeMetadata
-               */
-               if(version >= 14)
-               {
-                       if(version <= 15)
-                       {
-                               try{
-                                       std::ostringstream oss(std::ios_base::binary);
-                                       m_node_metadata->serialize(oss);
-                                       os<<serializeString(oss.str());
-                               }
-                               // This will happen if the string is longer than 65535
-                               catch(SerializationError &e)
-                               {
-                                       // Use an empty string
-                                       os<<serializeString("");
-                               }
-                       }
-                       else
-                       {
-                               std::ostringstream oss(std::ios_base::binary);
-                               m_node_metadata->serialize(oss);
-                               compressZlib(oss.str(), os);
-                               //os<<serializeLongString(oss.str());
-                       }
-               }
+void MapBlock::serializeNetworkSpecific(std::ostream &os)
+{
+       if (!data) {
+               throw SerializationError("ERROR: Not writing dummy block.");
        }
+
+       writeU8(os, 2); // version
 }
 
-void MapBlock::deSerialize(std::istream &is, u8 version)
+void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
 {
        if(!ser_ver_supported(version))
                throw VersionMismatchException("ERROR: MapBlock format not supported");
 
-       // These have no lighting info
-       if(version <= 1)
+       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
+
+       m_day_night_differs_expired = false;
+
+       if(version <= 21)
        {
-               setLightingExpired(true);
+               deSerialize_pre22(in_compressed, version, disk);
+               return;
+       }
+
+       // Decompress the whole block (version >= 29)
+       std::stringstream in_raw(std::ios_base::binary | std::ios_base::in | std::ios_base::out);
+       if (version >= 29)
+               decompress(in_compressed, in_raw, version);
+       std::istream &is = version >= 29 ? in_raw : in_compressed;
+
+       u8 flags = readU8(is);
+       is_underground = (flags & 0x01) != 0;
+       m_day_night_differs = (flags & 0x02) != 0;
+       if (version < 27)
+               m_lighting_complete = 0xFFFF;
+       else
+               m_lighting_complete = readU16(is);
+       m_generated = (flags & 0x08) == 0;
+
+       NameIdMapping nimap;
+       if (disk && version >= 29) {
+               // Timestamp
+               TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                               <<": Timestamp"<<std::endl);
+               setTimestampNoChangedFlag(readU32(is));
+               m_disk_timestamp = m_timestamp;
+
+               // Node/id mapping
+               TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                               <<": NameIdMapping"<<std::endl);
+               nimap.deSerialize(is);
        }
 
-       // These have no "generated" field
-       if(version < 18)
+       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                       <<": Bulk node data"<<std::endl);
+       u8 content_width = readU8(is);
+       u8 params_width = readU8(is);
+       if(content_width != 1 && content_width != 2)
+               throw SerializationError("MapBlock::deSerialize(): invalid content_width");
+       if(params_width != 2)
+               throw SerializationError("MapBlock::deSerialize(): invalid params_width");
+
+       /*
+               Bulk node data
+       */
+       if (version >= 29) {
+               MapNode::deSerializeBulk(is, version, data, nodecount,
+                       content_width, params_width);
+       } else {
+               // use in_raw from above to avoid allocating another stream object
+               decompress(is, in_raw, version);
+               MapNode::deSerializeBulk(in_raw, version, data, nodecount,
+                       content_width, params_width);
+       }
+
+       /*
+               NodeMetadata
+       */
+       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                       <<": Node metadata"<<std::endl);
+       if (version >= 29) {
+               m_node_metadata.deSerialize(is, m_gamedef->idef());
+       } else {
+               try {
+                       // reuse in_raw
+                       in_raw.str("");
+                       in_raw.clear();
+                       decompress(is, in_raw, version);
+                       if (version >= 23)
+                               m_node_metadata.deSerialize(in_raw, m_gamedef->idef());
+                       else
+                               content_nodemeta_deserialize_legacy(in_raw,
+                                       &m_node_metadata, &m_node_timers,
+                                       m_gamedef->idef());
+               } catch(SerializationError &e) {
+                       warningstream<<"MapBlock::deSerialize(): Ignoring an error"
+                                       <<" while deserializing node metadata at ("
+                                       <<PP(getPos())<<": "<<e.what()<<std::endl;
+               }
+       }
+
+       /*
+               Data that is only on disk
+       */
+       if(disk)
        {
-               m_generated = true;
+               // Node timers
+               if(version == 23){
+                       // Read unused zero
+                       readU8(is);
+               }
+               if(version == 24){
+                       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                                       <<": Node timers (ver==24)"<<std::endl);
+                       m_node_timers.deSerialize(is, version);
+               }
+
+               // Static objects
+               TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                               <<": Static objects"<<std::endl);
+               m_static_objects.deSerialize(is);
+
+               if(version < 29) {
+                       // Timestamp
+                       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                                   <<": Timestamp"<<std::endl);
+                       setTimestampNoChangedFlag(readU32(is));
+                       m_disk_timestamp = m_timestamp;
+
+                       // Node/id mapping
+                       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                                   <<": NameIdMapping"<<std::endl);
+                       nimap.deSerialize(is);
+               }
+
+               // Dynamically re-set ids based on node names
+               correctBlockNodeIds(&nimap, data, m_gamedef);
+
+               if(version >= 25){
+                       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                                       <<": Node timers (ver>=25)"<<std::endl);
+                       m_node_timers.deSerialize(is, version);
+               }
        }
 
+       TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
+                       <<": Done."<<std::endl);
+}
+
+void MapBlock::deSerializeNetworkSpecific(std::istream &is)
+{
+       try {
+               readU8(is);
+               //const u8 version = readU8(is);
+               //if (version != 1)
+                       //throw SerializationError("unsupported MapBlock version");
+
+       } catch(SerializationError &e) {
+               warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
+                               <<": "<<e.what()<<std::endl;
+       }
+}
+
+/*
+       Legacy serialization
+*/
+
+void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
+{
+       // Initialize default flags
+       is_underground = false;
+       m_day_night_differs = false;
+       m_lighting_complete = 0xFFFF;
+       m_generated = true;
+
+       // Make a temporary buffer
+       u32 ser_length = MapNode::serializedLength(version);
+       SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
+
        // These have no compression
-       if(version <= 3 || version == 5 || version == 6)
-       {
-               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
+       if (version <= 3 || version == 5 || version == 6) {
                char tmp;
                is.read(&tmp, 1);
-               if(is.gcount() != 1)
-                       throw SerializationError
-                                       ("MapBlock::deSerialize: no enough input data");
+               if (is.gcount() != 1)
+                       throw SerializationError(std::string(FUNCTION_NAME)
+                               + ": not enough input data");
                is_underground = tmp;
-               for(u32 i=0; i<nodecount; i++)
-               {
-                       s32 len = MapNode::serializedLength(version);
-                       SharedBuffer<u8> d(len);
-                       is.read((char*)*d, len);
-                       if(is.gcount() != len)
-                               throw SerializationError
-                                               ("MapBlock::deSerialize: no enough input data");
-                       data[i].deSerialize(*d, version);
-               }
-       }
-       else if(version <= 10)
-       {
-               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
-
+               is.read((char *)*databuf_nodelist, nodecount * ser_length);
+               if ((u32)is.gcount() != nodecount * ser_length)
+                       throw SerializationError(std::string(FUNCTION_NAME)
+                               + ": not enough input data");
+       } else if (version <= 10) {
                u8 t8;
-               is.read((char*)&t8, 1);
+               is.read((char *)&t8, 1);
                is_underground = t8;
 
                {
@@ -778,12 +641,11 @@ void MapBlock::deSerialize(std::istream &is, u8 version)
                        std::ostringstream os(std::ios_base::binary);
                        decompress(is, os, version);
                        std::string s = os.str();
-                       if(s.size() != nodecount)
-                               throw SerializationError
-                                               ("MapBlock::deSerialize: invalid format");
-                       for(u32 i=0; i<s.size(); i++)
-                       {
-                               data[i].param0 = s[i];
+                       if (s.size() != nodecount)
+                               throw SerializationError(std::string(FUNCTION_NAME)
+                                       + ": not enough input data");
+                       for (u32 i = 0; i < s.size(); i++) {
+                               databuf_nodelist[i*ser_length] = s[i];
                        }
                }
                {
@@ -791,161 +653,166 @@ void MapBlock::deSerialize(std::istream &is, u8 version)
                        std::ostringstream os(std::ios_base::binary);
                        decompress(is, os, version);
                        std::string s = os.str();
-                       if(s.size() != nodecount)
-                               throw SerializationError
-                                               ("MapBlock::deSerialize: invalid format");
-                       for(u32 i=0; i<s.size(); i++)
-                       {
-                               data[i].param1 = s[i];
+                       if (s.size() != nodecount)
+                               throw SerializationError(std::string(FUNCTION_NAME)
+                                       + ": not enough input data");
+                       for (u32 i = 0; i < s.size(); i++) {
+                               databuf_nodelist[i*ser_length + 1] = s[i];
                        }
                }
-       
-               if(version >= 10)
-               {
+
+               if (version >= 10) {
                        // Uncompress and set param2 data
                        std::ostringstream os(std::ios_base::binary);
                        decompress(is, os, version);
                        std::string s = os.str();
-                       if(s.size() != nodecount)
-                               throw SerializationError
-                                               ("MapBlock::deSerialize: invalid format");
-                       for(u32 i=0; i<s.size(); i++)
-                       {
-                               data[i].param2 = s[i];
+                       if (s.size() != nodecount)
+                               throw SerializationError(std::string(FUNCTION_NAME)
+                                       + ": not enough input data");
+                       for (u32 i = 0; i < s.size(); i++) {
+                               databuf_nodelist[i*ser_length + 2] = s[i];
                        }
                }
-       }
-       // All other versions (newest)
-       else
-       {
-               u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
-
+       } else { // All other versions (10 to 21)
                u8 flags;
                is.read((char*)&flags, 1);
-               is_underground = (flags & 0x01) ? true : false;
-               m_day_night_differs = (flags & 0x02) ? true : false;
-               m_lighting_expired = (flags & 0x04) ? true : false;
+               is_underground = (flags & 0x01) != 0;
+               m_day_night_differs = (flags & 0x02) != 0;
                if(version >= 18)
-                       m_generated = (flags & 0x08) ? false : true;
+                       m_generated = (flags & 0x08) == 0;
 
                // Uncompress data
                std::ostringstream os(std::ios_base::binary);
                decompress(is, os, version);
                std::string s = os.str();
-               if(s.size() != nodecount*3)
-                       throw SerializationError
-                                       ("MapBlock::deSerialize: decompress resulted in size"
-                                       " other than nodecount*3");
+               if (s.size() != nodecount * 3)
+                       throw SerializationError(std::string(FUNCTION_NAME)
+                               + ": decompress resulted in size other than nodecount*3");
 
                // deserialize nodes from buffer
-               for(u32 i=0; i<nodecount; i++)
-               {
-                       u8 buf[3];
-                       buf[0] = s[i];
-                       buf[1] = s[i+nodecount];
-                       buf[2] = s[i+nodecount*2];
-                       data[i].deSerialize(buf, version);
+               for (u32 i = 0; i < nodecount; i++) {
+                       databuf_nodelist[i*ser_length] = s[i];
+                       databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
+                       databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
                }
-               
+
                /*
                        NodeMetadata
                */
-               if(version >= 14)
-               {
+               if (version >= 14) {
                        // Ignore errors
-                       try{
-                               if(version <= 15)
-                               {
-                                       std::string data = deSerializeString(is);
+                       try {
+                               if (version <= 15) {
+                                       std::string data = deSerializeString16(is);
                                        std::istringstream iss(data, std::ios_base::binary);
-                                       m_node_metadata->deSerialize(iss, m_gamedef);
-                               }
-                               else
-                               {
-                                       //std::string data = deSerializeLongString(is);
+                                       content_nodemeta_deserialize_legacy(iss,
+                                               &m_node_metadata, &m_node_timers,
+                                               m_gamedef->idef());
+                               } else {
+                                       //std::string data = deSerializeString32(is);
                                        std::ostringstream oss(std::ios_base::binary);
                                        decompressZlib(is, oss);
                                        std::istringstream iss(oss.str(), std::ios_base::binary);
-                                       m_node_metadata->deSerialize(iss, m_gamedef);
+                                       content_nodemeta_deserialize_legacy(iss,
+                                               &m_node_metadata, &m_node_timers,
+                                               m_gamedef->idef());
                                }
-                       }
-                       catch(SerializationError &e)
-                       {
-                               errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
+                       } catch(SerializationError &e) {
+                               warningstream<<"MapBlock::deSerialize(): Ignoring an error"
                                                <<" while deserializing node metadata"<<std::endl;
                        }
                }
        }
-}
 
-void MapBlock::serializeDiskExtra(std::ostream &os, u8 version)
-{
-       // Versions up from 9 have block objects. (DEPRECATED)
-       if(version >= 9)
-       {
-               // count=0
-               writeU16(os, 0);
-       }
-       
-       // Versions up from 15 have static objects.
-       if(version >= 15)
-       {
-               m_static_objects.serialize(os);
+       // Deserialize node data
+       for (u32 i = 0; i < nodecount; i++) {
+               data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
        }
 
-       // Timestamp
-       if(version >= 17)
-       {
-               writeU32(os, getTimestamp());
-       }
+       if (disk) {
+               /*
+                       Versions up from 9 have block objects. (DEPRECATED)
+               */
+               if (version >= 9) {
+                       u16 count = readU16(is);
+                       // Not supported and length not known if count is not 0
+                       if(count != 0){
+                               warningstream<<"MapBlock::deSerialize_pre22(): "
+                                               <<"Ignoring stuff coming at and after MBOs"<<std::endl;
+                               return;
+                       }
+               }
+
+               /*
+                       Versions up from 15 have static objects.
+               */
+               if (version >= 15)
+                       m_static_objects.deSerialize(is);
+
+               // Timestamp
+               if (version >= 17) {
+                       setTimestampNoChangedFlag(readU32(is));
+                       m_disk_timestamp = m_timestamp;
+               } else {
+                       setTimestampNoChangedFlag(BLOCK_TIMESTAMP_UNDEFINED);
+               }
 
-       // Scan and write node definition id mapping
-       if(version >= 21){
+               // Dynamically re-set ids based on node names
                NameIdMapping nimap;
-               getBlockNodeIdMapping(&nimap, this, m_gamedef->ndef());
-               nimap.serialize(os);
+               // If supported, read node definition id mapping
+               if (version >= 21) {
+                       nimap.deSerialize(is);
+               // Else set the legacy mapping
+               } else {
+                       content_mapnode_get_name_id_mapping(&nimap);
+               }
+               correctBlockNodeIds(&nimap, data, m_gamedef);
        }
-}
 
-void MapBlock::deSerializeDiskExtra(std::istream &is, u8 version)
-{
-       /*
-               Versions up from 9 have block objects. (DEPRECATED)
-       */
-       if(version >= 9){
-               u16 count = readU16(is);
-               // Not supported and length not known if count is not 0
-               if(count != 0){
-                       errorstream<<"WARNING: MapBlock::deSerializeDiskExtra(): "
-                                       <<"Ignoring stuff coming at and after MBOs"<<std::endl;
-                       return;
+
+       // Legacy data changes
+       // This code has to convert from pre-22 to post-22 format.
+       const NodeDefManager *nodedef = m_gamedef->ndef();
+       for(u32 i=0; i<nodecount; i++)
+       {
+               const ContentFeatures &f = nodedef->get(data[i].getContent());
+               // Mineral
+               if(nodedef->getId("default:stone") == data[i].getContent()
+                               && data[i].getParam1() == 1)
+               {
+                       data[i].setContent(nodedef->getId("default:stone_with_coal"));
+                       data[i].setParam1(0);
+               }
+               else if(nodedef->getId("default:stone") == data[i].getContent()
+                               && data[i].getParam1() == 2)
+               {
+                       data[i].setContent(nodedef->getId("default:stone_with_iron"));
+                       data[i].setParam1(0);
+               }
+               // facedir_simple
+               if(f.legacy_facedir_simple)
+               {
+                       data[i].setParam2(data[i].getParam1());
+                       data[i].setParam1(0);
+               }
+               // wall_mounted
+               if(f.legacy_wallmounted)
+               {
+                       u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
+                       u8 dir_old_format = data[i].getParam2();
+                       u8 dir_new_format = 0;
+                       for(u8 j=0; j<8; j++)
+                       {
+                               if((dir_old_format & wallmounted_new_to_old[j]) != 0)
+                               {
+                                       dir_new_format = j;
+                                       break;
+                               }
+                       }
+                       data[i].setParam2(dir_new_format);
                }
        }
 
-       /*
-               Versions up from 15 have static objects.
-       */
-       if(version >= 15)
-               m_static_objects.deSerialize(is);
-               
-       // Timestamp
-       if(version >= 17){
-               setTimestamp(readU32(is));
-               m_disk_timestamp = m_timestamp;
-       } else {
-               setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
-       }
-       
-       // Dynamically re-set ids based on node names
-       NameIdMapping nimap;
-       // If supported, read node definition id mapping
-       if(version >= 21){
-               nimap.deSerialize(is);
-       // Else set the legacy mapping
-       } else {
-               content_mapnode_get_name_id_mapping(&nimap);
-       }
-       correctBlockNodeIds(&nimap, this, m_gamedef);
 }
 
 /*
@@ -957,12 +824,12 @@ std::string analyze_block(MapBlock *block)
                return "NULL";
 
        std::ostringstream desc;
-       
+
        v3s16 p = block->getPos();
-       char spos[20];
-       snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
+       char spos[25];
+       porting::mt_snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
        desc<<spos;
-       
+
        switch(block->getModified())
        {
        case MOD_STATE_CLEAN:
@@ -988,17 +855,7 @@ std::string analyze_block(MapBlock *block)
        else
                desc<<"is_ug [ ], ";
 
-#ifndef SERVER
-       if(block->getMeshExpired())
-               desc<<"mesh_exp [X], ";
-       else
-               desc<<"mesh_exp [ ], ";
-#endif
-
-       if(block->getLightingExpired())
-               desc<<"lighting_exp [X], ";
-       else
-               desc<<"lighting_exp [ ], ";
+       desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
 
        if(block->isDummy())
        {
@@ -1015,7 +872,7 @@ std::string analyze_block(MapBlock *block)
                for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
                {
                        v3s16 p(x0,y0,z0);
-                       MapNode n = block->getNode(p);
+                       MapNode n = block->getNodeNoEx(p);
                        content_t c = n.getContent();
                        if(c == CONTENT_IGNORE)
                                some_ignore = true;
@@ -1026,21 +883,21 @@ std::string analyze_block(MapBlock *block)
                        else
                                full_air = false;
                }
-               
+
                desc<<"content {";
-               
+
                std::ostringstream ss;
-               
+
                if(full_ignore)
                        ss<<"IGNORE (full), ";
                else if(some_ignore)
                        ss<<"IGNORE, ";
-               
+
                if(full_air)
                        ss<<"AIR (full), ";
                else if(some_air)
                        ss<<"AIR, ";
-               
+
                if(ss.str().size()>=2)
                        desc<<ss.str().substr(0, ss.str().size()-2);