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.
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"
38 #include "util/serialize.h"
40 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
46 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
50 m_modified(MOD_STATE_WRITE_NEEDED),
51 m_modified_reason("initial"),
52 m_modified_reason_too_long(false),
53 is_underground(false),
54 m_lighting_expired(true),
55 m_day_night_differs(false),
56 m_day_night_differs_expired(true),
58 m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
59 m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
77 //JMutexAutoLock lock(mesh_mutex);
91 bool MapBlock::isValidPositionParent(v3s16 p)
93 if(isValidPosition(p))
98 return m_parent->isValidPosition(getPosRelative() + p);
102 MapNode MapBlock::getNodeParent(v3s16 p)
104 if(isValidPosition(p) == false)
106 return m_parent->getNode(getPosRelative() + p);
111 throw InvalidPositionException();
112 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
116 void MapBlock::setNodeParent(v3s16 p, MapNode & n)
118 if(isValidPosition(p) == false)
120 m_parent->setNode(getPosRelative() + p, n);
125 throw InvalidPositionException();
126 data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
130 MapNode MapBlock::getNodeParentNoEx(v3s16 p)
132 if(isValidPosition(p) == false)
135 return m_parent->getNode(getPosRelative() + p);
137 catch(InvalidPositionException &e)
139 return MapNode(CONTENT_IGNORE);
146 return MapNode(CONTENT_IGNORE);
148 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
153 Propagates sunlight down through the block.
154 Doesn't modify nodes that are not affected by sunlight.
156 Returns false if sunlight at bottom block is invalid.
157 Returns true if sunlight at bottom block is valid.
158 Returns true if bottom block doesn't exist.
160 If there is a block above, continues from it.
161 If there is no block above, assumes there is sunlight, unless
162 is_underground is set or highest node is water.
164 All sunlighted nodes are added to light_sources.
166 if remove_light==true, sets non-sunlighted nodes black.
168 if black_air_left!=NULL, it is set to true if non-sunlighted
169 air is left in block.
171 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
172 bool remove_light, bool *black_air_left)
174 INodeDefManager *nodemgr = m_gamedef->ndef();
176 // Whether the sunlight at the top of the bottom block is valid
177 bool block_below_is_valid = true;
179 v3s16 pos_relative = getPosRelative();
181 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
183 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
186 bool no_sunlight = false;
187 bool no_top_block = false;
188 // Check if node above block has sunlight
190 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
191 if(n.getContent() == CONTENT_IGNORE)
194 no_sunlight = is_underground;
196 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
201 catch(InvalidPositionException &e)
205 // NOTE: This makes over-ground roofed places sunlighted
206 // Assume sunlight, unless is_underground==true
213 MapNode n = getNode(v3s16(x, MAP_BLOCKSIZE-1, z));
214 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
219 // NOTE: As of now, this just would make everything dark.
221 //no_sunlight = true;
224 #if 0 // Doesn't work; nothing gets light.
225 bool no_sunlight = true;
226 bool no_top_block = false;
227 // Check if node above block has sunlight
229 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
230 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
235 catch(InvalidPositionException &e)
241 /*std::cout<<"("<<x<<","<<z<<"): "
242 <<"no_top_block="<<no_top_block
243 <<", is_underground="<<is_underground
244 <<", no_sunlight="<<no_sunlight
247 s16 y = MAP_BLOCKSIZE-1;
249 // This makes difference to diminishing in water.
250 bool stopped_to_solid_object = false;
252 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
257 MapNode &n = getNodeRef(pos);
259 if(current_light == 0)
263 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
265 // Do nothing: Sunlight is continued
267 else if(nodemgr->get(n).light_propagates == false)
269 // A solid object is on the way.
270 stopped_to_solid_object = true;
278 current_light = diminish_light(current_light);
281 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
283 if(current_light > old_light || remove_light)
285 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
288 if(diminish_light(current_light) != 0)
290 light_sources.insert(pos_relative + pos);
293 if(current_light == 0 && stopped_to_solid_object)
297 *black_air_left = true;
302 // Whether or not the block below should see LIGHT_SUN
303 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
306 If the block below hasn't already been marked invalid:
308 Check if the node below the block has proper sunlight at top.
309 If not, the block below is invalid.
311 Ignore non-transparent nodes as they always have no light
315 if(block_below_is_valid)
317 MapNode n = getNodeParent(v3s16(x, -1, z));
318 if(nodemgr->get(n).light_propagates)
320 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
321 && sunlight_should_go_down == false)
322 block_below_is_valid = false;
323 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
324 && sunlight_should_go_down == true)
325 block_below_is_valid = false;
329 catch(InvalidPositionException &e)
331 /*std::cout<<"InvalidBlockException for bottom block node"
333 // Just no block below, no need to panic.
338 return block_below_is_valid;
342 void MapBlock::copyTo(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 data to VoxelManipulator
348 dst.copyFrom(data, data_area, v3s16(0,0,0),
349 getPosRelative(), data_size);
352 void MapBlock::copyFrom(VoxelManipulator &dst)
354 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
355 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
357 // Copy from VoxelManipulator to data
358 dst.copyTo(data, data_area, v3s16(0,0,0),
359 getPosRelative(), data_size);
362 void MapBlock::actuallyUpdateDayNightDiff()
364 INodeDefManager *nodemgr = m_gamedef->ndef();
365 // Running this function un-expires m_day_night_differs
366 m_day_night_differs_expired = false;
370 m_day_night_differs = false;
374 bool differs = false;
377 Check if any lighting value differs
379 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
381 MapNode &n = data[i];
382 if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr))
390 If some lighting values differ, check if the whole thing is
391 just air. If it is, differ = false
395 bool only_air = true;
396 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
398 MapNode &n = data[i];
399 if(n.getContent() != CONTENT_AIR)
409 // Set member variable
410 m_day_night_differs = differs;
413 void MapBlock::expireDayNightDiff()
415 //INodeDefManager *nodemgr = m_gamedef->ndef();
418 m_day_night_differs = false;
419 m_day_night_differs_expired = false;
423 m_day_night_differs_expired = true;
426 s16 MapBlock::getGroundLevel(v2s16 p2d)
432 s16 y = MAP_BLOCKSIZE-1;
435 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
436 if(m_gamedef->ndef()->get(n).walkable)
438 if(y == MAP_BLOCKSIZE-1)
446 catch(InvalidPositionException &e)
455 // List relevant id-name pairs for ids in the block using nodedef
456 // Renumbers the content IDs (starting at 0 and incrementing
457 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
458 INodeDefManager *nodedef)
460 std::map<content_t, content_t> mapping;
461 std::set<content_t> unknown_contents;
462 content_t id_counter = 0;
463 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
465 content_t global_id = nodes[i].getContent();
466 content_t id = CONTENT_IGNORE;
468 // Try to find an existing mapping
469 std::map<content_t, content_t>::iterator j = mapping.find(global_id);
470 if(j != mapping.end())
476 // We have to assign a new mapping
478 mapping.insert(std::make_pair(global_id, id));
480 const ContentFeatures &f = nodedef->get(global_id);
481 const std::string &name = f.name;
483 unknown_contents.insert(global_id);
485 nimap->set(id, name);
488 // Update the MapNode
489 nodes[i].setContent(id);
491 for(std::set<content_t>::const_iterator
492 i = unknown_contents.begin();
493 i != unknown_contents.end(); i++){
494 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
495 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
498 // Correct ids in the block to match nodedef based on names.
499 // Unknown ones are added to nodedef.
500 // Will not update itself to match id-name pairs in nodedef.
501 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
504 INodeDefManager *nodedef = gamedef->ndef();
505 // This means the block contains incorrect ids, and we contain
506 // the information to convert those to names.
507 // nodedef contains information to convert our names to globally
509 std::set<content_t> unnamed_contents;
510 std::set<std::string> unallocatable_contents;
511 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
513 content_t local_id = nodes[i].getContent();
515 bool found = nimap->getName(local_id, name);
517 unnamed_contents.insert(local_id);
521 found = nodedef->getId(name, global_id);
523 global_id = gamedef->allocateUnknownNodeId(name);
524 if(global_id == CONTENT_IGNORE){
525 unallocatable_contents.insert(name);
529 nodes[i].setContent(global_id);
531 for(std::set<content_t>::const_iterator
532 i = unnamed_contents.begin();
533 i != unnamed_contents.end(); i++){
534 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
535 <<"Block contains id "<<(*i)
536 <<" with no name mapping"<<std::endl;
538 for(std::set<std::string>::const_iterator
539 i = unallocatable_contents.begin();
540 i != unallocatable_contents.end(); i++){
541 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
542 <<"Could not allocate global id for node name \""
543 <<(*i)<<"\""<<std::endl;
547 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
549 if(!ser_ver_supported(version))
550 throw VersionMismatchException("ERROR: MapBlock format not supported");
554 throw SerializationError("ERROR: Not writing dummy block.");
557 // Can't do this anymore; we have 16-bit dynamically allocated node IDs
558 // in memory; conversion just won't work in this direction.
560 throw SerializationError("MapBlock::serialize: serialization to "
561 "version < 24 not possible");
567 if(getDayNightDiff())
569 if(m_lighting_expired)
571 if(m_generated == false)
579 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
582 MapNode *tmp_nodes = new MapNode[nodecount];
583 for(u32 i=0; i<nodecount; i++)
584 tmp_nodes[i] = data[i];
585 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
587 u8 content_width = 2;
589 writeU8(os, content_width);
590 writeU8(os, params_width);
591 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
592 content_width, params_width, true);
597 u8 content_width = 2;
599 writeU8(os, content_width);
600 writeU8(os, params_width);
601 MapNode::serializeBulk(os, version, data, nodecount,
602 content_width, params_width, true);
608 std::ostringstream oss(std::ios_base::binary);
609 m_node_metadata.serialize(oss);
610 compressZlib(oss.str(), os);
613 Data that goes to disk, but not the network
619 m_node_timers.serialize(os, version);
623 m_static_objects.serialize(os);
626 writeU32(os, getTimestamp());
628 // Write block-specific node definition id mapping
633 m_node_timers.serialize(os, version);
638 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
640 if(!ser_ver_supported(version))
641 throw VersionMismatchException("ERROR: MapBlock format not supported");
643 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
645 m_day_night_differs_expired = false;
649 deSerialize_pre22(is, version, disk);
653 u8 flags = readU8(is);
654 is_underground = (flags & 0x01) ? true : false;
655 m_day_night_differs = (flags & 0x02) ? true : false;
656 m_lighting_expired = (flags & 0x04) ? true : false;
657 m_generated = (flags & 0x08) ? false : true;
662 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
663 <<": Bulk node data"<<std::endl);
664 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
665 u8 content_width = readU8(is);
666 u8 params_width = readU8(is);
667 if(content_width != 1 && content_width != 2)
668 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
669 if(params_width != 2)
670 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
671 MapNode::deSerializeBulk(is, version, data, nodecount,
672 content_width, params_width, true);
677 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
678 <<": Node metadata"<<std::endl);
681 std::ostringstream oss(std::ios_base::binary);
682 decompressZlib(is, oss);
683 std::istringstream iss(oss.str(), std::ios_base::binary);
685 m_node_metadata.deSerialize(iss, m_gamedef);
687 content_nodemeta_deserialize_legacy(iss,
688 &m_node_metadata, &m_node_timers,
691 catch(SerializationError &e)
693 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
694 <<" while deserializing node metadata at ("
695 <<PP(getPos())<<": "<<e.what()<<std::endl;
699 Data that is only on disk
709 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
710 <<": Node timers (ver==24)"<<std::endl);
711 m_node_timers.deSerialize(is, version);
715 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
716 <<": Static objects"<<std::endl);
717 m_static_objects.deSerialize(is);
720 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
721 <<": Timestamp"<<std::endl);
722 setTimestamp(readU32(is));
723 m_disk_timestamp = m_timestamp;
725 // Dynamically re-set ids based on node names
726 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
727 <<": NameIdMapping"<<std::endl);
729 nimap.deSerialize(is);
730 correctBlockNodeIds(&nimap, data, m_gamedef);
733 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
734 <<": Node timers (ver>=25)"<<std::endl);
735 m_node_timers.deSerialize(is, version);
739 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
740 <<": Done."<<std::endl);
747 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
749 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
751 // Initialize default flags
752 is_underground = false;
753 m_day_night_differs = false;
754 m_lighting_expired = false;
757 // Make a temporary buffer
758 u32 ser_length = MapNode::serializedLength(version);
759 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
761 // These have no compression
762 if(version <= 3 || version == 5 || version == 6)
767 throw SerializationError
768 ("MapBlock::deSerialize: no enough input data");
769 is_underground = tmp;
770 is.read((char*)*databuf_nodelist, nodecount * ser_length);
771 if((u32)is.gcount() != nodecount * ser_length)
772 throw SerializationError
773 ("MapBlock::deSerialize: no enough input data");
775 else if(version <= 10)
778 is.read((char*)&t8, 1);
782 // Uncompress and set material data
783 std::ostringstream os(std::ios_base::binary);
784 decompress(is, os, version);
785 std::string s = os.str();
786 if(s.size() != nodecount)
787 throw SerializationError
788 ("MapBlock::deSerialize: invalid format");
789 for(u32 i=0; i<s.size(); i++)
791 databuf_nodelist[i*ser_length] = s[i];
795 // Uncompress and set param data
796 std::ostringstream os(std::ios_base::binary);
797 decompress(is, os, version);
798 std::string s = os.str();
799 if(s.size() != nodecount)
800 throw SerializationError
801 ("MapBlock::deSerialize: invalid format");
802 for(u32 i=0; i<s.size(); i++)
804 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
816 ("MapBlock::deSerialize: invalid format");
817 for(u32 i=0; i<s.size(); i++)
819 databuf_nodelist[i*ser_length + 2] = s[i];
823 // All other versions (newest)
827 is.read((char*)&flags, 1);
828 is_underground = (flags & 0x01) ? true : false;
829 m_day_night_differs = (flags & 0x02) ? true : false;
830 m_lighting_expired = (flags & 0x04) ? true : false;
832 m_generated = (flags & 0x08) ? false : true;
835 std::ostringstream os(std::ios_base::binary);
836 decompress(is, os, version);
837 std::string s = os.str();
838 if(s.size() != nodecount*3)
839 throw SerializationError
840 ("MapBlock::deSerialize: decompress resulted in size"
841 " other than nodecount*3");
843 // deserialize nodes from buffer
844 for(u32 i=0; i<nodecount; i++)
846 databuf_nodelist[i*ser_length] = s[i];
847 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
848 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
860 std::string data = deSerializeString(is);
861 std::istringstream iss(data, std::ios_base::binary);
862 content_nodemeta_deserialize_legacy(iss,
863 &m_node_metadata, &m_node_timers,
868 //std::string data = deSerializeLongString(is);
869 std::ostringstream oss(std::ios_base::binary);
870 decompressZlib(is, oss);
871 std::istringstream iss(oss.str(), std::ios_base::binary);
872 content_nodemeta_deserialize_legacy(iss,
873 &m_node_metadata, &m_node_timers,
877 catch(SerializationError &e)
879 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
880 <<" while deserializing node metadata"<<std::endl;
885 // Deserialize node data
886 for(u32 i=0; i<nodecount; i++)
888 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
894 Versions up from 9 have block objects. (DEPRECATED)
897 u16 count = readU16(is);
898 // Not supported and length not known if count is not 0
900 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
901 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
907 Versions up from 15 have static objects.
910 m_static_objects.deSerialize(is);
914 setTimestamp(readU32(is));
915 m_disk_timestamp = m_timestamp;
917 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
920 // Dynamically re-set ids based on node names
922 // If supported, read node definition id mapping
924 nimap.deSerialize(is);
925 // Else set the legacy mapping
927 content_mapnode_get_name_id_mapping(&nimap);
929 correctBlockNodeIds(&nimap, data, m_gamedef);
933 // Legacy data changes
934 // This code has to convert from pre-22 to post-22 format.
935 INodeDefManager *nodedef = m_gamedef->ndef();
936 for(u32 i=0; i<nodecount; i++)
938 const ContentFeatures &f = nodedef->get(data[i].getContent());
940 if(nodedef->getId("default:stone") == data[i].getContent()
941 && data[i].getParam1() == 1)
943 data[i].setContent(nodedef->getId("default:stone_with_coal"));
944 data[i].setParam1(0);
946 else if(nodedef->getId("default:stone") == data[i].getContent()
947 && data[i].getParam1() == 2)
949 data[i].setContent(nodedef->getId("default:stone_with_iron"));
950 data[i].setParam1(0);
953 if(f.legacy_facedir_simple)
955 data[i].setParam2(data[i].getParam1());
956 data[i].setParam1(0);
959 if(f.legacy_wallmounted)
961 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
962 u8 dir_old_format = data[i].getParam2();
963 u8 dir_new_format = 0;
964 for(u8 j=0; j<8; j++)
966 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
972 data[i].setParam2(dir_new_format);
979 Get a quick string to describe what a block actually contains
981 std::string analyze_block(MapBlock *block)
986 std::ostringstream desc;
988 v3s16 p = block->getPos();
990 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
993 switch(block->getModified())
995 case MOD_STATE_CLEAN:
998 case MOD_STATE_WRITE_AT_UNLOAD:
999 desc<<"WRITE_AT_UNLOAD, ";
1001 case MOD_STATE_WRITE_NEEDED:
1002 desc<<"WRITE_NEEDED, ";
1005 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1008 if(block->isGenerated())
1009 desc<<"is_gen [X], ";
1011 desc<<"is_gen [ ], ";
1013 if(block->getIsUnderground())
1014 desc<<"is_ug [X], ";
1016 desc<<"is_ug [ ], ";
1018 if(block->getLightingExpired())
1019 desc<<"lighting_exp [X], ";
1021 desc<<"lighting_exp [ ], ";
1023 if(block->isDummy())
1029 bool full_ignore = true;
1030 bool some_ignore = false;
1031 bool full_air = true;
1032 bool some_air = false;
1033 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1034 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1035 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1038 MapNode n = block->getNode(p);
1039 content_t c = n.getContent();
1040 if(c == CONTENT_IGNORE)
1043 full_ignore = false;
1044 if(c == CONTENT_AIR)
1052 std::ostringstream ss;
1055 ss<<"IGNORE (full), ";
1056 else if(some_ignore)
1064 if(ss.str().size()>=2)
1065 desc<<ss.str().substr(0, ss.str().size()-2);
1070 return desc.str().substr(0, desc.str().size()-2);