3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "nodemetadata.h"
29 #include "nameidmapping.h"
30 #include "content_mapnode.h" // For legacy name-id mapping
31 #include "content_nodemeta.h" // For legacy deserialization
32 #include "serialization.h"
34 #include "mapblock_mesh.h"
36 #include "util/string.h"
37 #include "util/serialize.h"
39 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
45 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
49 m_modified(MOD_STATE_WRITE_NEEDED),
50 m_modified_reason("initial"),
51 m_modified_reason_too_long(false),
52 is_underground(false),
53 m_lighting_expired(true),
54 m_day_night_differs(false),
55 m_day_night_differs_expired(true),
57 m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
58 m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
75 //JMutexAutoLock lock(mesh_mutex);
89 bool MapBlock::isValidPositionParent(v3s16 p)
91 if(isValidPosition(p))
96 return m_parent->isValidPosition(getPosRelative() + p);
100 MapNode MapBlock::getNodeParent(v3s16 p)
102 if(isValidPosition(p) == false)
104 return m_parent->getNode(getPosRelative() + p);
109 throw InvalidPositionException();
110 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
114 void MapBlock::setNodeParent(v3s16 p, MapNode & n)
116 if(isValidPosition(p) == false)
118 m_parent->setNode(getPosRelative() + p, n);
123 throw InvalidPositionException();
124 data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
128 MapNode MapBlock::getNodeParentNoEx(v3s16 p)
130 if(isValidPosition(p) == false)
133 return m_parent->getNode(getPosRelative() + p);
135 catch(InvalidPositionException &e)
137 return MapNode(CONTENT_IGNORE);
144 return MapNode(CONTENT_IGNORE);
146 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
151 Propagates sunlight down through the block.
152 Doesn't modify nodes that are not affected by sunlight.
154 Returns false if sunlight at bottom block is invalid.
155 Returns true if sunlight at bottom block is valid.
156 Returns true if bottom block doesn't exist.
158 If there is a block above, continues from it.
159 If there is no block above, assumes there is sunlight, unless
160 is_underground is set or highest node is water.
162 All sunlighted nodes are added to light_sources.
164 if remove_light==true, sets non-sunlighted nodes black.
166 if black_air_left!=NULL, it is set to true if non-sunlighted
167 air is left in block.
169 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
170 bool remove_light, bool *black_air_left)
172 INodeDefManager *nodemgr = m_gamedef->ndef();
174 // Whether the sunlight at the top of the bottom block is valid
175 bool block_below_is_valid = true;
177 v3s16 pos_relative = getPosRelative();
179 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
181 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
184 bool no_sunlight = false;
185 bool no_top_block = false;
186 // Check if node above block has sunlight
188 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
189 if(n.getContent() == CONTENT_IGNORE)
192 no_sunlight = is_underground;
194 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
199 catch(InvalidPositionException &e)
203 // NOTE: This makes over-ground roofed places sunlighted
204 // Assume sunlight, unless is_underground==true
211 MapNode n = getNode(v3s16(x, MAP_BLOCKSIZE-1, z));
212 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
217 // NOTE: As of now, this just would make everything dark.
219 //no_sunlight = true;
222 #if 0 // Doesn't work; nothing gets light.
223 bool no_sunlight = true;
224 bool no_top_block = false;
225 // Check if node above block has sunlight
227 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
228 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
233 catch(InvalidPositionException &e)
239 /*std::cout<<"("<<x<<","<<z<<"): "
240 <<"no_top_block="<<no_top_block
241 <<", is_underground="<<is_underground
242 <<", no_sunlight="<<no_sunlight
245 s16 y = MAP_BLOCKSIZE-1;
247 // This makes difference to diminishing in water.
248 bool stopped_to_solid_object = false;
250 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
255 MapNode &n = getNodeRef(pos);
257 if(current_light == 0)
261 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
263 // Do nothing: Sunlight is continued
265 else if(nodemgr->get(n).light_propagates == false)
267 // A solid object is on the way.
268 stopped_to_solid_object = true;
276 current_light = diminish_light(current_light);
279 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
281 if(current_light > old_light || remove_light)
283 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
286 if(diminish_light(current_light) != 0)
288 light_sources.insert(pos_relative + pos);
291 if(current_light == 0 && stopped_to_solid_object)
295 *black_air_left = true;
300 // Whether or not the block below should see LIGHT_SUN
301 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
304 If the block below hasn't already been marked invalid:
306 Check if the node below the block has proper sunlight at top.
307 If not, the block below is invalid.
309 Ignore non-transparent nodes as they always have no light
313 if(block_below_is_valid)
315 MapNode n = getNodeParent(v3s16(x, -1, z));
316 if(nodemgr->get(n).light_propagates)
318 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
319 && sunlight_should_go_down == false)
320 block_below_is_valid = false;
321 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
322 && sunlight_should_go_down == true)
323 block_below_is_valid = false;
327 catch(InvalidPositionException &e)
329 /*std::cout<<"InvalidBlockException for bottom block node"
331 // Just no block below, no need to panic.
336 return block_below_is_valid;
340 void MapBlock::copyTo(VoxelManipulator &dst)
342 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
343 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
345 // Copy from data to VoxelManipulator
346 dst.copyFrom(data, data_area, v3s16(0,0,0),
347 getPosRelative(), data_size);
350 void MapBlock::copyFrom(VoxelManipulator &dst)
352 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
353 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
355 // Copy from VoxelManipulator to data
356 dst.copyTo(data, data_area, v3s16(0,0,0),
357 getPosRelative(), data_size);
360 void MapBlock::actuallyUpdateDayNightDiff()
362 INodeDefManager *nodemgr = m_gamedef->ndef();
363 // Running this function un-expires m_day_night_differs
364 m_day_night_differs_expired = false;
368 m_day_night_differs = false;
372 bool differs = false;
375 Check if any lighting value differs
377 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
379 MapNode &n = data[i];
380 if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr))
388 If some lighting values differ, check if the whole thing is
389 just air. If it is, differ = false
393 bool only_air = true;
394 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
396 MapNode &n = data[i];
397 if(n.getContent() != CONTENT_AIR)
407 // Set member variable
408 m_day_night_differs = differs;
411 void MapBlock::expireDayNightDiff()
413 //INodeDefManager *nodemgr = m_gamedef->ndef();
416 m_day_night_differs = false;
417 m_day_night_differs_expired = false;
421 m_day_night_differs_expired = true;
424 s16 MapBlock::getGroundLevel(v2s16 p2d)
430 s16 y = MAP_BLOCKSIZE-1;
433 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
434 if(m_gamedef->ndef()->get(n).walkable)
436 if(y == MAP_BLOCKSIZE-1)
444 catch(InvalidPositionException &e)
453 // List relevant id-name pairs for ids in the block using nodedef
454 // Renumbers the content IDs (starting at 0 and incrementing
455 // use static memory requires about 65535 * sizeof(int) ram in order to be
456 // sure we can handle all content ids. But it's absolutely worth it as it's
457 // a speedup of 4 for one of the major time consuming functions on storing
459 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX];
460 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
461 INodeDefManager *nodedef)
463 memset(getBlockNodeIdMapping_mapping, 0xFF, USHRT_MAX * sizeof(content_t));
465 std::set<content_t> unknown_contents;
466 content_t id_counter = 0;
467 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
469 content_t global_id = nodes[i].getContent();
470 content_t id = CONTENT_IGNORE;
472 // Try to find an existing mapping
473 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
474 id = getBlockNodeIdMapping_mapping[global_id];
478 // We have to assign a new mapping
480 getBlockNodeIdMapping_mapping[global_id] = id;
482 const ContentFeatures &f = nodedef->get(global_id);
483 const std::string &name = f.name;
485 unknown_contents.insert(global_id);
487 nimap->set(id, name);
490 // Update the MapNode
491 nodes[i].setContent(id);
493 for(std::set<content_t>::const_iterator
494 i = unknown_contents.begin();
495 i != unknown_contents.end(); i++){
496 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
497 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
500 // Correct ids in the block to match nodedef based on names.
501 // Unknown ones are added to nodedef.
502 // Will not update itself to match id-name pairs in nodedef.
503 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
506 INodeDefManager *nodedef = gamedef->ndef();
507 // This means the block contains incorrect ids, and we contain
508 // the information to convert those to names.
509 // nodedef contains information to convert our names to globally
511 std::set<content_t> unnamed_contents;
512 std::set<std::string> unallocatable_contents;
513 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
515 content_t local_id = nodes[i].getContent();
517 bool found = nimap->getName(local_id, name);
519 unnamed_contents.insert(local_id);
523 found = nodedef->getId(name, global_id);
525 global_id = gamedef->allocateUnknownNodeId(name);
526 if(global_id == CONTENT_IGNORE){
527 unallocatable_contents.insert(name);
531 nodes[i].setContent(global_id);
533 for(std::set<content_t>::const_iterator
534 i = unnamed_contents.begin();
535 i != unnamed_contents.end(); i++){
536 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
537 <<"Block contains id "<<(*i)
538 <<" with no name mapping"<<std::endl;
540 for(std::set<std::string>::const_iterator
541 i = unallocatable_contents.begin();
542 i != unallocatable_contents.end(); i++){
543 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
544 <<"Could not allocate global id for node name \""
545 <<(*i)<<"\""<<std::endl;
549 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
551 if(!ser_ver_supported(version))
552 throw VersionMismatchException("ERROR: MapBlock format not supported");
556 throw SerializationError("ERROR: Not writing dummy block.");
559 // Can't do this anymore; we have 16-bit dynamically allocated node IDs
560 // in memory; conversion just won't work in this direction.
562 throw SerializationError("MapBlock::serialize: serialization to "
563 "version < 24 not possible");
569 if(getDayNightDiff())
571 if(m_lighting_expired)
573 if(m_generated == false)
581 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
584 MapNode *tmp_nodes = new MapNode[nodecount];
585 for(u32 i=0; i<nodecount; i++)
586 tmp_nodes[i] = data[i];
587 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
589 u8 content_width = 2;
591 writeU8(os, content_width);
592 writeU8(os, params_width);
593 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
594 content_width, params_width, true);
599 u8 content_width = 2;
601 writeU8(os, content_width);
602 writeU8(os, params_width);
603 MapNode::serializeBulk(os, version, data, nodecount,
604 content_width, params_width, true);
610 std::ostringstream oss(std::ios_base::binary);
611 m_node_metadata.serialize(oss);
612 compressZlib(oss.str(), os);
615 Data that goes to disk, but not the network
621 m_node_timers.serialize(os, version);
625 m_static_objects.serialize(os);
628 writeU32(os, getTimestamp());
630 // Write block-specific node definition id mapping
635 m_node_timers.serialize(os, version);
640 void MapBlock::serializeNetworkSpecific(std::ostream &os, u16 net_proto_version)
644 throw SerializationError("ERROR: Not writing dummy block.");
647 if(net_proto_version >= 21){
649 writeU8(os, version);
650 writeF1000(os, 0); // deprecated heat
651 writeF1000(os, 0); // deprecated humidity
655 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
657 if(!ser_ver_supported(version))
658 throw VersionMismatchException("ERROR: MapBlock format not supported");
660 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
662 m_day_night_differs_expired = false;
666 deSerialize_pre22(is, version, disk);
670 u8 flags = readU8(is);
671 is_underground = (flags & 0x01) ? true : false;
672 m_day_night_differs = (flags & 0x02) ? true : false;
673 m_lighting_expired = (flags & 0x04) ? true : false;
674 m_generated = (flags & 0x08) ? false : true;
679 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
680 <<": Bulk node data"<<std::endl);
681 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
682 u8 content_width = readU8(is);
683 u8 params_width = readU8(is);
684 if(content_width != 1 && content_width != 2)
685 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
686 if(params_width != 2)
687 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
688 MapNode::deSerializeBulk(is, version, data, nodecount,
689 content_width, params_width, true);
694 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
695 <<": Node metadata"<<std::endl);
698 std::ostringstream oss(std::ios_base::binary);
699 decompressZlib(is, oss);
700 std::istringstream iss(oss.str(), std::ios_base::binary);
702 m_node_metadata.deSerialize(iss, m_gamedef);
704 content_nodemeta_deserialize_legacy(iss,
705 &m_node_metadata, &m_node_timers,
708 catch(SerializationError &e)
710 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
711 <<" while deserializing node metadata at ("
712 <<PP(getPos())<<": "<<e.what()<<std::endl;
716 Data that is only on disk
726 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
727 <<": Node timers (ver==24)"<<std::endl);
728 m_node_timers.deSerialize(is, version);
732 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
733 <<": Static objects"<<std::endl);
734 m_static_objects.deSerialize(is);
737 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
738 <<": Timestamp"<<std::endl);
739 setTimestamp(readU32(is));
740 m_disk_timestamp = m_timestamp;
742 // Dynamically re-set ids based on node names
743 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
744 <<": NameIdMapping"<<std::endl);
746 nimap.deSerialize(is);
747 correctBlockNodeIds(&nimap, data, m_gamedef);
750 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
751 <<": Node timers (ver>=25)"<<std::endl);
752 m_node_timers.deSerialize(is, version);
756 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
757 <<": Done."<<std::endl);
760 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
763 int version = readU8(is);
765 // throw SerializationError("unsupported MapBlock version");
767 readF1000(is); // deprecated heat
768 readF1000(is); // deprecated humidity
771 catch(SerializationError &e)
773 errorstream<<"WARNING: MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
774 <<": "<<e.what()<<std::endl;
782 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
784 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
786 // Initialize default flags
787 is_underground = false;
788 m_day_night_differs = false;
789 m_lighting_expired = false;
792 // Make a temporary buffer
793 u32 ser_length = MapNode::serializedLength(version);
794 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
796 // These have no compression
797 if(version <= 3 || version == 5 || version == 6)
802 throw SerializationError
803 ("MapBlock::deSerialize: no enough input data");
804 is_underground = tmp;
805 is.read((char*)*databuf_nodelist, nodecount * ser_length);
806 if((u32)is.gcount() != nodecount * ser_length)
807 throw SerializationError
808 ("MapBlock::deSerialize: no enough input data");
810 else if(version <= 10)
813 is.read((char*)&t8, 1);
817 // Uncompress and set material data
818 std::ostringstream os(std::ios_base::binary);
819 decompress(is, os, version);
820 std::string s = os.str();
821 if(s.size() != nodecount)
822 throw SerializationError
823 ("MapBlock::deSerialize: invalid format");
824 for(u32 i=0; i<s.size(); i++)
826 databuf_nodelist[i*ser_length] = s[i];
830 // Uncompress and set param data
831 std::ostringstream os(std::ios_base::binary);
832 decompress(is, os, version);
833 std::string s = os.str();
834 if(s.size() != nodecount)
835 throw SerializationError
836 ("MapBlock::deSerialize: invalid format");
837 for(u32 i=0; i<s.size(); i++)
839 databuf_nodelist[i*ser_length + 1] = s[i];
845 // Uncompress and set param2 data
846 std::ostringstream os(std::ios_base::binary);
847 decompress(is, os, version);
848 std::string s = os.str();
849 if(s.size() != nodecount)
850 throw SerializationError
851 ("MapBlock::deSerialize: invalid format");
852 for(u32 i=0; i<s.size(); i++)
854 databuf_nodelist[i*ser_length + 2] = s[i];
858 // All other versions (newest)
862 is.read((char*)&flags, 1);
863 is_underground = (flags & 0x01) ? true : false;
864 m_day_night_differs = (flags & 0x02) ? true : false;
865 m_lighting_expired = (flags & 0x04) ? true : false;
867 m_generated = (flags & 0x08) ? false : true;
870 std::ostringstream os(std::ios_base::binary);
871 decompress(is, os, version);
872 std::string s = os.str();
873 if(s.size() != nodecount*3)
874 throw SerializationError
875 ("MapBlock::deSerialize: decompress resulted in size"
876 " other than nodecount*3");
878 // deserialize nodes from buffer
879 for(u32 i=0; i<nodecount; i++)
881 databuf_nodelist[i*ser_length] = s[i];
882 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
883 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
895 std::string data = deSerializeString(is);
896 std::istringstream iss(data, std::ios_base::binary);
897 content_nodemeta_deserialize_legacy(iss,
898 &m_node_metadata, &m_node_timers,
903 //std::string data = deSerializeLongString(is);
904 std::ostringstream oss(std::ios_base::binary);
905 decompressZlib(is, oss);
906 std::istringstream iss(oss.str(), std::ios_base::binary);
907 content_nodemeta_deserialize_legacy(iss,
908 &m_node_metadata, &m_node_timers,
912 catch(SerializationError &e)
914 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
915 <<" while deserializing node metadata"<<std::endl;
920 // Deserialize node data
921 for(u32 i=0; i<nodecount; i++)
923 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
929 Versions up from 9 have block objects. (DEPRECATED)
932 u16 count = readU16(is);
933 // Not supported and length not known if count is not 0
935 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
936 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
942 Versions up from 15 have static objects.
945 m_static_objects.deSerialize(is);
949 setTimestamp(readU32(is));
950 m_disk_timestamp = m_timestamp;
952 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
955 // Dynamically re-set ids based on node names
957 // If supported, read node definition id mapping
959 nimap.deSerialize(is);
960 // Else set the legacy mapping
962 content_mapnode_get_name_id_mapping(&nimap);
964 correctBlockNodeIds(&nimap, data, m_gamedef);
968 // Legacy data changes
969 // This code has to convert from pre-22 to post-22 format.
970 INodeDefManager *nodedef = m_gamedef->ndef();
971 for(u32 i=0; i<nodecount; i++)
973 const ContentFeatures &f = nodedef->get(data[i].getContent());
975 if(nodedef->getId("default:stone") == data[i].getContent()
976 && data[i].getParam1() == 1)
978 data[i].setContent(nodedef->getId("default:stone_with_coal"));
979 data[i].setParam1(0);
981 else if(nodedef->getId("default:stone") == data[i].getContent()
982 && data[i].getParam1() == 2)
984 data[i].setContent(nodedef->getId("default:stone_with_iron"));
985 data[i].setParam1(0);
988 if(f.legacy_facedir_simple)
990 data[i].setParam2(data[i].getParam1());
991 data[i].setParam1(0);
994 if(f.legacy_wallmounted)
996 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
997 u8 dir_old_format = data[i].getParam2();
998 u8 dir_new_format = 0;
999 for(u8 j=0; j<8; j++)
1001 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
1007 data[i].setParam2(dir_new_format);
1014 Get a quick string to describe what a block actually contains
1016 std::string analyze_block(MapBlock *block)
1021 std::ostringstream desc;
1023 v3s16 p = block->getPos();
1025 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
1028 switch(block->getModified())
1030 case MOD_STATE_CLEAN:
1033 case MOD_STATE_WRITE_AT_UNLOAD:
1034 desc<<"WRITE_AT_UNLOAD, ";
1036 case MOD_STATE_WRITE_NEEDED:
1037 desc<<"WRITE_NEEDED, ";
1040 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1043 if(block->isGenerated())
1044 desc<<"is_gen [X], ";
1046 desc<<"is_gen [ ], ";
1048 if(block->getIsUnderground())
1049 desc<<"is_ug [X], ";
1051 desc<<"is_ug [ ], ";
1053 if(block->getLightingExpired())
1054 desc<<"lighting_exp [X], ";
1056 desc<<"lighting_exp [ ], ";
1058 if(block->isDummy())
1064 bool full_ignore = true;
1065 bool some_ignore = false;
1066 bool full_air = true;
1067 bool some_air = false;
1068 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1069 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1070 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1073 MapNode n = block->getNode(p);
1074 content_t c = n.getContent();
1075 if(c == CONTENT_IGNORE)
1078 full_ignore = false;
1079 if(c == CONTENT_AIR)
1087 std::ostringstream ss;
1090 ss<<"IGNORE (full), ";
1091 else if(some_ignore)
1099 if(ss.str().size()>=2)
1100 desc<<ss.str().substr(0, ss.str().size()-2);
1105 return desc.str().substr(0, desc.str().size()-2);