3 Copyright (C) 2010 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.
28 #include "nodemetadata.h"
31 #include "nameidmapping.h"
32 #include "content_mapnode.h" // For legacy name-id mapping
33 #include "content_nodemeta.h" // For legacy deserialization
35 #include "mapblock_mesh.h"
37 #include "util/string.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),
76 //JMutexAutoLock lock(mesh_mutex);
90 bool MapBlock::isValidPositionParent(v3s16 p)
92 if(isValidPosition(p))
97 return m_parent->isValidPosition(getPosRelative() + p);
101 MapNode MapBlock::getNodeParent(v3s16 p)
103 if(isValidPosition(p) == false)
105 return m_parent->getNode(getPosRelative() + p);
110 throw InvalidPositionException();
111 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
115 void MapBlock::setNodeParent(v3s16 p, MapNode & n)
117 if(isValidPosition(p) == false)
119 m_parent->setNode(getPosRelative() + p, n);
124 throw InvalidPositionException();
125 data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
129 MapNode MapBlock::getNodeParentNoEx(v3s16 p)
131 if(isValidPosition(p) == false)
134 return m_parent->getNode(getPosRelative() + p);
136 catch(InvalidPositionException &e)
138 return MapNode(CONTENT_IGNORE);
145 return MapNode(CONTENT_IGNORE);
147 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
152 Propagates sunlight down through the block.
153 Doesn't modify nodes that are not affected by sunlight.
155 Returns false if sunlight at bottom block is invalid.
156 Returns true if sunlight at bottom block is valid.
157 Returns true if bottom block doesn't exist.
159 If there is a block above, continues from it.
160 If there is no block above, assumes there is sunlight, unless
161 is_underground is set or highest node is water.
163 All sunlighted nodes are added to light_sources.
165 if remove_light==true, sets non-sunlighted nodes black.
167 if black_air_left!=NULL, it is set to true if non-sunlighted
168 air is left in block.
170 bool MapBlock::propagateSunlight(core::map<v3s16, bool> & light_sources,
171 bool remove_light, bool *black_air_left)
173 INodeDefManager *nodemgr = m_gamedef->ndef();
175 // Whether the sunlight at the top of the bottom block is valid
176 bool block_below_is_valid = true;
178 v3s16 pos_relative = getPosRelative();
180 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
182 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
185 bool no_sunlight = false;
186 bool no_top_block = false;
187 // Check if node above block has sunlight
189 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
190 if(n.getContent() == CONTENT_IGNORE)
193 no_sunlight = is_underground;
195 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
200 catch(InvalidPositionException &e)
204 // NOTE: This makes over-ground roofed places sunlighted
205 // Assume sunlight, unless is_underground==true
212 MapNode n = getNode(v3s16(x, MAP_BLOCKSIZE-1, z));
213 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
218 // NOTE: As of now, this just would make everything dark.
220 //no_sunlight = true;
223 #if 0 // Doesn't work; nothing gets light.
224 bool no_sunlight = true;
225 bool no_top_block = false;
226 // Check if node above block has sunlight
228 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
229 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
234 catch(InvalidPositionException &e)
240 /*std::cout<<"("<<x<<","<<z<<"): "
241 <<"no_top_block="<<no_top_block
242 <<", is_underground="<<is_underground
243 <<", no_sunlight="<<no_sunlight
246 s16 y = MAP_BLOCKSIZE-1;
248 // This makes difference to diminishing in water.
249 bool stopped_to_solid_object = false;
251 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
256 MapNode &n = getNodeRef(pos);
258 if(current_light == 0)
262 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
264 // Do nothing: Sunlight is continued
266 else if(nodemgr->get(n).light_propagates == false)
268 // A solid object is on the way.
269 stopped_to_solid_object = true;
277 current_light = diminish_light(current_light);
280 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
282 if(current_light > old_light || remove_light)
284 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
287 if(diminish_light(current_light) != 0)
289 light_sources.insert(pos_relative + pos, true);
292 if(current_light == 0 && stopped_to_solid_object)
296 *black_air_left = true;
301 // Whether or not the block below should see LIGHT_SUN
302 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
305 If the block below hasn't already been marked invalid:
307 Check if the node below the block has proper sunlight at top.
308 If not, the block below is invalid.
310 Ignore non-transparent nodes as they always have no light
314 if(block_below_is_valid)
316 MapNode n = getNodeParent(v3s16(x, -1, z));
317 if(nodemgr->get(n).light_propagates)
319 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
320 && sunlight_should_go_down == false)
321 block_below_is_valid = false;
322 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
323 && sunlight_should_go_down == true)
324 block_below_is_valid = false;
328 catch(InvalidPositionException &e)
330 /*std::cout<<"InvalidBlockException for bottom block node"
332 // Just no block below, no need to panic.
337 return block_below_is_valid;
341 void MapBlock::copyTo(VoxelManipulator &dst)
343 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
344 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
346 // Copy from data to VoxelManipulator
347 dst.copyFrom(data, data_area, v3s16(0,0,0),
348 getPosRelative(), data_size);
351 void MapBlock::copyFrom(VoxelManipulator &dst)
353 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
354 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
356 // Copy from VoxelManipulator to data
357 dst.copyTo(data, data_area, v3s16(0,0,0),
358 getPosRelative(), data_size);
361 void MapBlock::actuallyUpdateDayNightDiff()
363 INodeDefManager *nodemgr = m_gamedef->ndef();
364 // Running this function un-expires m_day_night_differs
365 m_day_night_differs_expired = false;
369 m_day_night_differs = false;
373 bool differs = false;
376 Check if any lighting value differs
378 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
380 MapNode &n = data[i];
381 if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr))
389 If some lighting values differ, check if the whole thing is
390 just air. If it is, differ = false
394 bool only_air = true;
395 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
397 MapNode &n = data[i];
398 if(n.getContent() != CONTENT_AIR)
408 // Set member variable
409 m_day_night_differs = differs;
412 void MapBlock::expireDayNightDiff()
414 //INodeDefManager *nodemgr = m_gamedef->ndef();
417 m_day_night_differs = false;
418 m_day_night_differs_expired = false;
422 m_day_night_differs_expired = true;
425 s16 MapBlock::getGroundLevel(v2s16 p2d)
431 s16 y = MAP_BLOCKSIZE-1;
434 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
435 if(m_gamedef->ndef()->get(n).walkable)
437 if(y == MAP_BLOCKSIZE-1)
445 catch(InvalidPositionException &e)
454 // List relevant id-name pairs for ids in the block using nodedef
455 // Renumbers the content IDs (starting at 0 and incrementing
456 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
457 INodeDefManager *nodedef)
459 std::map<content_t, content_t> mapping;
460 std::set<content_t> unknown_contents;
461 content_t id_counter = 0;
462 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
464 content_t global_id = nodes[i].getContent();
465 content_t id = CONTENT_IGNORE;
467 // Try to find an existing mapping
468 std::map<content_t, content_t>::iterator j = mapping.find(global_id);
469 if(j != mapping.end())
475 // We have to assign a new mapping
477 mapping.insert(std::make_pair(global_id, id));
479 const ContentFeatures &f = nodedef->get(global_id);
480 const std::string &name = f.name;
482 unknown_contents.insert(global_id);
484 nimap->set(id, name);
487 // Update the MapNode
488 nodes[i].setContent(id);
490 for(std::set<content_t>::const_iterator
491 i = unknown_contents.begin();
492 i != unknown_contents.end(); i++){
493 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
494 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
497 // Correct ids in the block to match nodedef based on names.
498 // Unknown ones are added to nodedef.
499 // Will not update itself to match id-name pairs in nodedef.
500 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
503 INodeDefManager *nodedef = gamedef->ndef();
504 // This means the block contains incorrect ids, and we contain
505 // the information to convert those to names.
506 // nodedef contains information to convert our names to globally
508 std::set<content_t> unnamed_contents;
509 std::set<std::string> unallocatable_contents;
510 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
512 content_t local_id = nodes[i].getContent();
514 bool found = nimap->getName(local_id, name);
516 unnamed_contents.insert(local_id);
520 found = nodedef->getId(name, global_id);
522 global_id = gamedef->allocateUnknownNodeId(name);
523 if(global_id == CONTENT_IGNORE){
524 unallocatable_contents.insert(name);
528 nodes[i].setContent(global_id);
530 for(std::set<content_t>::const_iterator
531 i = unnamed_contents.begin();
532 i != unnamed_contents.end(); i++){
533 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
534 <<"Block contains id "<<(*i)
535 <<" with no name mapping"<<std::endl;
537 for(std::set<std::string>::const_iterator
538 i = unallocatable_contents.begin();
539 i != unallocatable_contents.end(); i++){
540 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
541 <<"Could not allocate global id for node name \""
542 <<(*i)<<"\""<<std::endl;
546 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
548 if(!ser_ver_supported(version))
549 throw VersionMismatchException("ERROR: MapBlock format not supported");
553 throw SerializationError("ERROR: Not writing dummy block.");
556 // Can't do this anymore; we have 16-bit dynamically allocated node IDs
557 // in memory; conversion just won't work in this direction.
559 throw SerializationError("MapBlock::serialize: serialization to "
560 "version < 24 not possible");
566 if(getDayNightDiff())
568 if(m_lighting_expired)
570 if(m_generated == false)
578 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
581 MapNode *tmp_nodes = new MapNode[nodecount];
582 for(u32 i=0; i<nodecount; i++)
583 tmp_nodes[i] = data[i];
584 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
586 u8 content_width = 2;
588 writeU8(os, content_width);
589 writeU8(os, params_width);
590 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
591 content_width, params_width, true);
596 u8 content_width = 2;
598 writeU8(os, content_width);
599 writeU8(os, params_width);
600 MapNode::serializeBulk(os, version, data, nodecount,
601 content_width, params_width, true);
607 std::ostringstream oss(std::ios_base::binary);
608 m_node_metadata.serialize(oss);
609 compressZlib(oss.str(), os);
612 Data that goes to disk, but not the network
618 m_node_timers.serialize(os, version);
622 m_static_objects.serialize(os);
625 writeU32(os, getTimestamp());
627 // Write block-specific node definition id mapping
632 m_node_timers.serialize(os, version);
637 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
639 if(!ser_ver_supported(version))
640 throw VersionMismatchException("ERROR: MapBlock format not supported");
642 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
644 m_day_night_differs_expired = false;
648 deSerialize_pre22(is, version, disk);
652 u8 flags = readU8(is);
653 is_underground = (flags & 0x01) ? true : false;
654 m_day_night_differs = (flags & 0x02) ? true : false;
655 m_lighting_expired = (flags & 0x04) ? true : false;
656 m_generated = (flags & 0x08) ? false : true;
661 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
662 <<": Bulk node data"<<std::endl);
663 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
664 u8 content_width = readU8(is);
665 u8 params_width = readU8(is);
666 if(content_width != 1 && content_width != 2)
667 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
668 if(params_width != 2)
669 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
670 MapNode::deSerializeBulk(is, version, data, nodecount,
671 content_width, params_width, true);
676 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
677 <<": Node metadata"<<std::endl);
680 std::ostringstream oss(std::ios_base::binary);
681 decompressZlib(is, oss);
682 std::istringstream iss(oss.str(), std::ios_base::binary);
684 m_node_metadata.deSerialize(iss, m_gamedef);
686 content_nodemeta_deserialize_legacy(iss,
687 &m_node_metadata, &m_node_timers,
690 catch(SerializationError &e)
692 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
693 <<" while deserializing node metadata at ("
694 <<PP(getPos())<<": "<<e.what()<<std::endl;
698 Data that is only on disk
708 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
709 <<": Node timers (ver==24)"<<std::endl);
710 m_node_timers.deSerialize(is, version);
714 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
715 <<": Static objects"<<std::endl);
716 m_static_objects.deSerialize(is);
719 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
720 <<": Timestamp"<<std::endl);
721 setTimestamp(readU32(is));
722 m_disk_timestamp = m_timestamp;
724 // Dynamically re-set ids based on node names
725 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
726 <<": NameIdMapping"<<std::endl);
728 nimap.deSerialize(is);
729 correctBlockNodeIds(&nimap, data, m_gamedef);
732 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
733 <<": Node timers (ver>=25)"<<std::endl);
734 m_node_timers.deSerialize(is, version);
738 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
739 <<": Done."<<std::endl);
746 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
748 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
750 // Initialize default flags
751 is_underground = false;
752 m_day_night_differs = false;
753 m_lighting_expired = false;
756 // Make a temporary buffer
757 u32 ser_length = MapNode::serializedLength(version);
758 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
760 // These have no compression
761 if(version <= 3 || version == 5 || version == 6)
766 throw SerializationError
767 ("MapBlock::deSerialize: no enough input data");
768 is_underground = tmp;
769 is.read((char*)*databuf_nodelist, nodecount * ser_length);
770 if((u32)is.gcount() != nodecount * ser_length)
771 throw SerializationError
772 ("MapBlock::deSerialize: no enough input data");
774 else if(version <= 10)
777 is.read((char*)&t8, 1);
781 // Uncompress and set material data
782 std::ostringstream os(std::ios_base::binary);
783 decompress(is, os, version);
784 std::string s = os.str();
785 if(s.size() != nodecount)
786 throw SerializationError
787 ("MapBlock::deSerialize: invalid format");
788 for(u32 i=0; i<s.size(); i++)
790 databuf_nodelist[i*ser_length] = s[i];
794 // Uncompress and set param data
795 std::ostringstream os(std::ios_base::binary);
796 decompress(is, os, version);
797 std::string s = os.str();
798 if(s.size() != nodecount)
799 throw SerializationError
800 ("MapBlock::deSerialize: invalid format");
801 for(u32 i=0; i<s.size(); i++)
803 databuf_nodelist[i*ser_length + 1] = s[i];
809 // Uncompress and set param2 data
810 std::ostringstream os(std::ios_base::binary);
811 decompress(is, os, version);
812 std::string s = os.str();
813 if(s.size() != nodecount)
814 throw SerializationError
815 ("MapBlock::deSerialize: invalid format");
816 for(u32 i=0; i<s.size(); i++)
818 databuf_nodelist[i*ser_length + 2] = s[i];
822 // All other versions (newest)
826 is.read((char*)&flags, 1);
827 is_underground = (flags & 0x01) ? true : false;
828 m_day_night_differs = (flags & 0x02) ? true : false;
829 m_lighting_expired = (flags & 0x04) ? true : false;
831 m_generated = (flags & 0x08) ? false : true;
834 std::ostringstream os(std::ios_base::binary);
835 decompress(is, os, version);
836 std::string s = os.str();
837 if(s.size() != nodecount*3)
838 throw SerializationError
839 ("MapBlock::deSerialize: decompress resulted in size"
840 " other than nodecount*3");
842 // deserialize nodes from buffer
843 for(u32 i=0; i<nodecount; i++)
845 databuf_nodelist[i*ser_length] = s[i];
846 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
847 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
859 std::string data = deSerializeString(is);
860 std::istringstream iss(data, std::ios_base::binary);
861 content_nodemeta_deserialize_legacy(iss,
862 &m_node_metadata, &m_node_timers,
867 //std::string data = deSerializeLongString(is);
868 std::ostringstream oss(std::ios_base::binary);
869 decompressZlib(is, oss);
870 std::istringstream iss(oss.str(), std::ios_base::binary);
871 content_nodemeta_deserialize_legacy(iss,
872 &m_node_metadata, &m_node_timers,
876 catch(SerializationError &e)
878 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
879 <<" while deserializing node metadata"<<std::endl;
884 // Deserialize node data
885 for(u32 i=0; i<nodecount; i++)
887 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
893 Versions up from 9 have block objects. (DEPRECATED)
896 u16 count = readU16(is);
897 // Not supported and length not known if count is not 0
899 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
900 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
906 Versions up from 15 have static objects.
909 m_static_objects.deSerialize(is);
913 setTimestamp(readU32(is));
914 m_disk_timestamp = m_timestamp;
916 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
919 // Dynamically re-set ids based on node names
921 // If supported, read node definition id mapping
923 nimap.deSerialize(is);
924 // Else set the legacy mapping
926 content_mapnode_get_name_id_mapping(&nimap);
928 correctBlockNodeIds(&nimap, data, m_gamedef);
932 // Legacy data changes
933 // This code has to convert from pre-22 to post-22 format.
934 INodeDefManager *nodedef = m_gamedef->ndef();
935 for(u32 i=0; i<nodecount; i++)
937 const ContentFeatures &f = nodedef->get(data[i].getContent());
939 if(nodedef->getId("default:stone") == data[i].getContent()
940 && data[i].getParam1() == 1)
942 data[i].setContent(nodedef->getId("default:stone_with_coal"));
943 data[i].setParam1(0);
945 else if(nodedef->getId("default:stone") == data[i].getContent()
946 && data[i].getParam1() == 2)
948 data[i].setContent(nodedef->getId("default:stone_with_iron"));
949 data[i].setParam1(0);
952 if(f.legacy_facedir_simple)
954 data[i].setParam2(data[i].getParam1());
955 data[i].setParam1(0);
958 if(f.legacy_wallmounted)
960 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
961 u8 dir_old_format = data[i].getParam2();
962 u8 dir_new_format = 0;
963 for(u8 j=0; j<8; j++)
965 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
971 data[i].setParam2(dir_new_format);
978 Get a quick string to describe what a block actually contains
980 std::string analyze_block(MapBlock *block)
985 std::ostringstream desc;
987 v3s16 p = block->getPos();
989 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
992 switch(block->getModified())
994 case MOD_STATE_CLEAN:
997 case MOD_STATE_WRITE_AT_UNLOAD:
998 desc<<"WRITE_AT_UNLOAD, ";
1000 case MOD_STATE_WRITE_NEEDED:
1001 desc<<"WRITE_NEEDED, ";
1004 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1007 if(block->isGenerated())
1008 desc<<"is_gen [X], ";
1010 desc<<"is_gen [ ], ";
1012 if(block->getIsUnderground())
1013 desc<<"is_ug [X], ";
1015 desc<<"is_ug [ ], ";
1017 if(block->getLightingExpired())
1018 desc<<"lighting_exp [X], ";
1020 desc<<"lighting_exp [ ], ";
1022 if(block->isDummy())
1028 bool full_ignore = true;
1029 bool some_ignore = false;
1030 bool full_air = true;
1031 bool some_air = false;
1032 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1033 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1034 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1037 MapNode n = block->getNode(p);
1038 content_t c = n.getContent();
1039 if(c == CONTENT_IGNORE)
1042 full_ignore = false;
1043 if(c == CONTENT_AIR)
1051 std::ostringstream ss;
1054 ss<<"IGNORE (full), ";
1055 else if(some_ignore)
1063 if(ss.str().size()>=2)
1064 desc<<ss.str().substr(0, ss.str().size()-2);
1069 return desc.str().substr(0, desc.str().size()-2);