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"
38 #include "util/basic_macros.h"
40 static const char *modified_reason_strings[] = {
49 "NodeMetaRef::reportMetadataChange",
51 "Timestamp expired (step)",
53 "removeRemovedObjects/remove",
54 "removeRemovedObjects/deactivate",
55 "Stored list cleared in activateObjects due to overflow",
56 "deactivateFarObjects: Static data moved in",
57 "deactivateFarObjects: Static data moved out",
58 "deactivateFarObjects: Static data changed considerably",
59 "finishBlockMake: expireDayNightDiff",
68 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
71 m_pos_relative(pos * MAP_BLOCKSIZE),
90 bool MapBlock::isValidPositionParent(v3s16 p)
92 if(isValidPosition(p))
97 return m_parent->isValidPosition(getPosRelative() + p);
101 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
103 if (isValidPosition(p) == false)
104 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
107 if (is_valid_position)
108 *is_valid_position = false;
109 return MapNode(CONTENT_IGNORE);
111 if (is_valid_position)
112 *is_valid_position = true;
113 return data[p.Z * zstride + p.Y * ystride + p.X];
116 std::string MapBlock::getModifiedReasonString()
120 const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
121 ARRLEN(modified_reason_strings));
123 for (u32 i = 0; i != ubound; i++) {
124 if ((m_modified_reason & (1 << i)) == 0)
127 reason += modified_reason_strings[i];
131 if (reason.length() > 2)
132 reason.resize(reason.length() - 2);
138 Propagates sunlight down through the block.
139 Doesn't modify nodes that are not affected by sunlight.
141 Returns false if sunlight at bottom block is invalid.
142 Returns true if sunlight at bottom block is valid.
143 Returns true if bottom block doesn't exist.
145 If there is a block above, continues from it.
146 If there is no block above, assumes there is sunlight, unless
147 is_underground is set or highest node is water.
149 All sunlighted nodes are added to light_sources.
151 if remove_light==true, sets non-sunlighted nodes black.
153 if black_air_left!=NULL, it is set to true if non-sunlighted
154 air is left in block.
156 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
157 bool remove_light, bool *black_air_left)
159 INodeDefManager *nodemgr = m_gamedef->ndef();
161 // Whether the sunlight at the top of the bottom block is valid
162 bool block_below_is_valid = true;
164 v3s16 pos_relative = getPosRelative();
166 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
168 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
171 bool no_sunlight = false;
172 //bool no_top_block = false;
174 // Check if node above block has sunlight
176 bool is_valid_position;
177 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
179 if (is_valid_position)
181 if(n.getContent() == CONTENT_IGNORE)
184 no_sunlight = is_underground;
186 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
193 //no_top_block = true;
195 // NOTE: This makes over-ground roofed places sunlighted
196 // Assume sunlight, unless is_underground==true
203 MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
204 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
209 // NOTE: As of now, this just would make everything dark.
211 //no_sunlight = true;
214 #if 0 // Doesn't work; nothing gets light.
215 bool no_sunlight = true;
216 bool no_top_block = false;
217 // Check if node above block has sunlight
219 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
220 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
225 catch(InvalidPositionException &e)
231 /*std::cout<<"("<<x<<","<<z<<"): "
232 <<"no_top_block="<<no_top_block
233 <<", is_underground="<<is_underground
234 <<", no_sunlight="<<no_sunlight
237 s16 y = MAP_BLOCKSIZE-1;
239 // This makes difference to diminishing in water.
240 bool stopped_to_solid_object = false;
242 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
247 MapNode &n = getNodeRef(pos);
249 if(current_light == 0)
253 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
255 // Do nothing: Sunlight is continued
257 else if(nodemgr->get(n).light_propagates == false)
259 // A solid object is on the way.
260 stopped_to_solid_object = true;
268 current_light = diminish_light(current_light);
271 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
273 if(current_light > old_light || remove_light)
275 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
278 if(diminish_light(current_light) != 0)
280 light_sources.insert(pos_relative + pos);
283 if(current_light == 0 && stopped_to_solid_object)
287 *black_air_left = true;
292 // Whether or not the block below should see LIGHT_SUN
293 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
296 If the block below hasn't already been marked invalid:
298 Check if the node below the block has proper sunlight at top.
299 If not, the block below is invalid.
301 Ignore non-transparent nodes as they always have no light
304 if(block_below_is_valid)
306 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
307 if (is_valid_position) {
308 if(nodemgr->get(n).light_propagates)
310 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
311 && sunlight_should_go_down == false)
312 block_below_is_valid = false;
313 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
314 && sunlight_should_go_down == true)
315 block_below_is_valid = false;
320 /*std::cout<<"InvalidBlockException for bottom block node"
322 // Just no block below, no need to panic.
328 return block_below_is_valid;
332 void MapBlock::copyTo(VoxelManipulator &dst)
334 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
335 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
337 // Copy from data to VoxelManipulator
338 dst.copyFrom(data, data_area, v3s16(0,0,0),
339 getPosRelative(), data_size);
342 void MapBlock::copyFrom(VoxelManipulator &dst)
344 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
345 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
347 // Copy from VoxelManipulator to data
348 dst.copyTo(data, data_area, v3s16(0,0,0),
349 getPosRelative(), data_size);
352 void MapBlock::actuallyUpdateDayNightDiff()
354 INodeDefManager *nodemgr = m_gamedef->ndef();
356 // Running this function un-expires m_day_night_differs
357 m_day_night_differs_expired = false;
360 m_day_night_differs = false;
367 Check if any lighting value differs
369 for (u32 i = 0; i < nodecount; i++) {
370 MapNode &n = data[i];
372 differs = !n.isLightDayNightEq(nodemgr);
378 If some lighting values differ, check if the whole thing is
379 just air. If it is just air, differs = false
382 bool only_air = true;
383 for (u32 i = 0; i < nodecount; i++) {
384 MapNode &n = data[i];
385 if (n.getContent() != CONTENT_AIR) {
394 // Set member variable
395 m_day_night_differs = differs;
398 void MapBlock::expireDayNightDiff()
401 m_day_night_differs = false;
402 m_day_night_differs_expired = false;
406 m_day_night_differs_expired = true;
409 s16 MapBlock::getGroundLevel(v2s16 p2d)
415 s16 y = MAP_BLOCKSIZE-1;
418 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
419 if(m_gamedef->ndef()->get(n).walkable)
421 if(y == MAP_BLOCKSIZE-1)
429 catch(InvalidPositionException &e)
438 // List relevant id-name pairs for ids in the block using nodedef
439 // Renumbers the content IDs (starting at 0 and incrementing
440 // use static memory requires about 65535 * sizeof(int) ram in order to be
441 // sure we can handle all content ids. But it's absolutely worth it as it's
442 // a speedup of 4 for one of the major time consuming functions on storing
444 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
445 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
446 INodeDefManager *nodedef)
448 memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
450 std::set<content_t> unknown_contents;
451 content_t id_counter = 0;
452 for (u32 i = 0; i < MapBlock::nodecount; i++) {
453 content_t global_id = nodes[i].getContent();
454 content_t id = CONTENT_IGNORE;
456 // Try to find an existing mapping
457 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
458 id = getBlockNodeIdMapping_mapping[global_id];
462 // We have to assign a new mapping
464 getBlockNodeIdMapping_mapping[global_id] = id;
466 const ContentFeatures &f = nodedef->get(global_id);
467 const std::string &name = f.name;
469 unknown_contents.insert(global_id);
471 nimap->set(id, name);
474 // Update the MapNode
475 nodes[i].setContent(id);
477 for(std::set<content_t>::const_iterator
478 i = unknown_contents.begin();
479 i != unknown_contents.end(); ++i){
480 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
481 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
484 // Correct ids in the block to match nodedef based on names.
485 // Unknown ones are added to nodedef.
486 // Will not update itself to match id-name pairs in nodedef.
487 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
490 INodeDefManager *nodedef = gamedef->ndef();
491 // This means the block contains incorrect ids, and we contain
492 // the information to convert those to names.
493 // nodedef contains information to convert our names to globally
495 std::set<content_t> unnamed_contents;
496 std::set<std::string> unallocatable_contents;
497 for (u32 i = 0; i < MapBlock::nodecount; i++) {
498 content_t local_id = nodes[i].getContent();
500 bool found = nimap->getName(local_id, name);
502 unnamed_contents.insert(local_id);
506 found = nodedef->getId(name, global_id);
508 global_id = gamedef->allocateUnknownNodeId(name);
509 if(global_id == CONTENT_IGNORE){
510 unallocatable_contents.insert(name);
514 nodes[i].setContent(global_id);
516 for(std::set<content_t>::const_iterator
517 i = unnamed_contents.begin();
518 i != unnamed_contents.end(); ++i){
519 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
520 <<"Block contains id "<<(*i)
521 <<" with no name mapping"<<std::endl;
523 for(std::set<std::string>::const_iterator
524 i = unallocatable_contents.begin();
525 i != unallocatable_contents.end(); ++i){
526 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
527 <<"Could not allocate global id for node name \""
528 <<(*i)<<"\""<<std::endl;
532 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
534 if(!ser_ver_supported(version))
535 throw VersionMismatchException("ERROR: MapBlock format not supported");
538 throw SerializationError("ERROR: Not writing dummy block.");
540 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
546 if(getDayNightDiff())
548 if(m_generated == false)
552 writeU16(os, m_lighting_complete);
561 MapNode *tmp_nodes = new MapNode[nodecount];
562 for(u32 i=0; i<nodecount; i++)
563 tmp_nodes[i] = data[i];
564 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
566 u8 content_width = 2;
568 writeU8(os, content_width);
569 writeU8(os, params_width);
570 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
571 content_width, params_width, true);
576 u8 content_width = 2;
578 writeU8(os, content_width);
579 writeU8(os, params_width);
580 MapNode::serializeBulk(os, version, data, nodecount,
581 content_width, params_width, true);
587 std::ostringstream oss(std::ios_base::binary);
588 m_node_metadata.serialize(oss, version, disk);
589 compressZlib(oss.str(), os);
592 Data that goes to disk, but not the network
598 m_node_timers.serialize(os, version);
602 m_static_objects.serialize(os);
605 writeU32(os, getTimestamp());
607 // Write block-specific node definition id mapping
612 m_node_timers.serialize(os, version);
617 void MapBlock::serializeNetworkSpecific(std::ostream &os)
620 throw SerializationError("ERROR: Not writing dummy block.");
623 writeU8(os, 1); // version
624 writeF1000(os, 0); // deprecated heat
625 writeF1000(os, 0); // deprecated humidity
628 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
630 if(!ser_ver_supported(version))
631 throw VersionMismatchException("ERROR: MapBlock format not supported");
633 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
635 m_day_night_differs_expired = false;
639 deSerialize_pre22(is, version, disk);
643 u8 flags = readU8(is);
644 is_underground = (flags & 0x01) ? true : false;
645 m_day_night_differs = (flags & 0x02) ? true : false;
647 m_lighting_complete = 0xFFFF;
649 m_lighting_complete = readU16(is);
650 m_generated = (flags & 0x08) ? false : true;
655 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
656 <<": Bulk node data"<<std::endl);
657 u8 content_width = readU8(is);
658 u8 params_width = readU8(is);
659 if(content_width != 1 && content_width != 2)
660 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
661 if(params_width != 2)
662 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
663 MapNode::deSerializeBulk(is, version, data, nodecount,
664 content_width, params_width, true);
669 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
670 <<": Node metadata"<<std::endl);
673 std::ostringstream oss(std::ios_base::binary);
674 decompressZlib(is, oss);
675 std::istringstream iss(oss.str(), std::ios_base::binary);
677 m_node_metadata.deSerialize(iss, m_gamedef->idef());
679 content_nodemeta_deserialize_legacy(iss,
680 &m_node_metadata, &m_node_timers,
682 } catch(SerializationError &e) {
683 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
684 <<" while deserializing node metadata at ("
685 <<PP(getPos())<<": "<<e.what()<<std::endl;
689 Data that is only on disk
699 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
700 <<": Node timers (ver==24)"<<std::endl);
701 m_node_timers.deSerialize(is, version);
705 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
706 <<": Static objects"<<std::endl);
707 m_static_objects.deSerialize(is);
710 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
711 <<": Timestamp"<<std::endl);
712 setTimestamp(readU32(is));
713 m_disk_timestamp = m_timestamp;
715 // Dynamically re-set ids based on node names
716 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
717 <<": NameIdMapping"<<std::endl);
719 nimap.deSerialize(is);
720 correctBlockNodeIds(&nimap, data, m_gamedef);
723 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
724 <<": Node timers (ver>=25)"<<std::endl);
725 m_node_timers.deSerialize(is, version);
729 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
730 <<": Done."<<std::endl);
733 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
736 int version = readU8(is);
738 // throw SerializationError("unsupported MapBlock version");
740 readF1000(is); // deprecated heat
741 readF1000(is); // deprecated humidity
744 catch(SerializationError &e)
746 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
747 <<": "<<e.what()<<std::endl;
755 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
757 // Initialize default flags
758 is_underground = false;
759 m_day_night_differs = false;
760 m_lighting_complete = 0xFFFF;
763 // Make a temporary buffer
764 u32 ser_length = MapNode::serializedLength(version);
765 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
767 // These have no compression
768 if (version <= 3 || version == 5 || version == 6) {
771 if (is.gcount() != 1)
772 throw SerializationError(std::string(FUNCTION_NAME)
773 + ": not enough input data");
774 is_underground = tmp;
775 is.read((char *)*databuf_nodelist, nodecount * ser_length);
776 if ((u32)is.gcount() != nodecount * ser_length)
777 throw SerializationError(std::string(FUNCTION_NAME)
778 + ": not enough input data");
779 } else if (version <= 10) {
781 is.read((char *)&t8, 1);
785 // Uncompress and set material data
786 std::ostringstream os(std::ios_base::binary);
787 decompress(is, os, version);
788 std::string s = os.str();
789 if (s.size() != nodecount)
790 throw SerializationError(std::string(FUNCTION_NAME)
791 + ": not enough input data");
792 for (u32 i = 0; i < s.size(); i++) {
793 databuf_nodelist[i*ser_length] = s[i];
797 // Uncompress and set param data
798 std::ostringstream os(std::ios_base::binary);
799 decompress(is, os, version);
800 std::string s = os.str();
801 if (s.size() != nodecount)
802 throw SerializationError(std::string(FUNCTION_NAME)
803 + ": not enough input data");
804 for (u32 i = 0; i < s.size(); i++) {
805 databuf_nodelist[i*ser_length + 1] = s[i];
810 // Uncompress and set param2 data
811 std::ostringstream os(std::ios_base::binary);
812 decompress(is, os, version);
813 std::string s = os.str();
814 if (s.size() != nodecount)
815 throw SerializationError(std::string(FUNCTION_NAME)
816 + ": not enough input data");
817 for (u32 i = 0; i < s.size(); i++) {
818 databuf_nodelist[i*ser_length + 2] = s[i];
821 } else { // All other versions (10 to 21)
823 is.read((char*)&flags, 1);
824 is_underground = (flags & 0x01) ? true : false;
825 m_day_night_differs = (flags & 0x02) ? true : false;
827 m_generated = (flags & 0x08) ? false : true;
830 std::ostringstream os(std::ios_base::binary);
831 decompress(is, os, version);
832 std::string s = os.str();
833 if (s.size() != nodecount * 3)
834 throw SerializationError(std::string(FUNCTION_NAME)
835 + ": decompress resulted in size other than nodecount*3");
837 // deserialize nodes from buffer
838 for (u32 i = 0; i < nodecount; i++) {
839 databuf_nodelist[i*ser_length] = s[i];
840 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
841 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
851 std::string data = deSerializeString(is);
852 std::istringstream iss(data, std::ios_base::binary);
853 content_nodemeta_deserialize_legacy(iss,
854 &m_node_metadata, &m_node_timers,
857 //std::string data = deSerializeLongString(is);
858 std::ostringstream oss(std::ios_base::binary);
859 decompressZlib(is, oss);
860 std::istringstream iss(oss.str(), std::ios_base::binary);
861 content_nodemeta_deserialize_legacy(iss,
862 &m_node_metadata, &m_node_timers,
865 } catch(SerializationError &e) {
866 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
867 <<" while deserializing node metadata"<<std::endl;
872 // Deserialize node data
873 for (u32 i = 0; i < nodecount; i++) {
874 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
879 Versions up from 9 have block objects. (DEPRECATED)
882 u16 count = readU16(is);
883 // Not supported and length not known if count is not 0
885 warningstream<<"MapBlock::deSerialize_pre22(): "
886 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
892 Versions up from 15 have static objects.
895 m_static_objects.deSerialize(is);
899 setTimestamp(readU32(is));
900 m_disk_timestamp = m_timestamp;
902 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
905 // Dynamically re-set ids based on node names
907 // If supported, read node definition id mapping
909 nimap.deSerialize(is);
910 // Else set the legacy mapping
912 content_mapnode_get_name_id_mapping(&nimap);
914 correctBlockNodeIds(&nimap, data, m_gamedef);
918 // Legacy data changes
919 // This code has to convert from pre-22 to post-22 format.
920 INodeDefManager *nodedef = m_gamedef->ndef();
921 for(u32 i=0; i<nodecount; i++)
923 const ContentFeatures &f = nodedef->get(data[i].getContent());
925 if(nodedef->getId("default:stone") == data[i].getContent()
926 && data[i].getParam1() == 1)
928 data[i].setContent(nodedef->getId("default:stone_with_coal"));
929 data[i].setParam1(0);
931 else if(nodedef->getId("default:stone") == data[i].getContent()
932 && data[i].getParam1() == 2)
934 data[i].setContent(nodedef->getId("default:stone_with_iron"));
935 data[i].setParam1(0);
938 if(f.legacy_facedir_simple)
940 data[i].setParam2(data[i].getParam1());
941 data[i].setParam1(0);
944 if(f.legacy_wallmounted)
946 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
947 u8 dir_old_format = data[i].getParam2();
948 u8 dir_new_format = 0;
949 for(u8 j=0; j<8; j++)
951 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
957 data[i].setParam2(dir_new_format);
964 Get a quick string to describe what a block actually contains
966 std::string analyze_block(MapBlock *block)
971 std::ostringstream desc;
973 v3s16 p = block->getPos();
975 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
978 switch(block->getModified())
980 case MOD_STATE_CLEAN:
983 case MOD_STATE_WRITE_AT_UNLOAD:
984 desc<<"WRITE_AT_UNLOAD, ";
986 case MOD_STATE_WRITE_NEEDED:
987 desc<<"WRITE_NEEDED, ";
990 desc<<"unknown getModified()="+itos(block->getModified())+", ";
993 if(block->isGenerated())
994 desc<<"is_gen [X], ";
996 desc<<"is_gen [ ], ";
998 if(block->getIsUnderground())
1001 desc<<"is_ug [ ], ";
1003 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1005 if(block->isDummy())
1011 bool full_ignore = true;
1012 bool some_ignore = false;
1013 bool full_air = true;
1014 bool some_air = false;
1015 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1016 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1017 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1020 MapNode n = block->getNodeNoEx(p);
1021 content_t c = n.getContent();
1022 if(c == CONTENT_IGNORE)
1025 full_ignore = false;
1026 if(c == CONTENT_AIR)
1034 std::ostringstream ss;
1037 ss<<"IGNORE (full), ";
1038 else if(some_ignore)
1046 if(ss.str().size()>=2)
1047 desc<<ss.str().substr(0, ss.str().size()-2);
1052 return desc.str().substr(0, desc.str().size()-2);