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),
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(core::map<v3s16, bool> & 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, true);
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 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
456 INodeDefManager *nodedef)
458 std::map<content_t, content_t> mapping;
459 std::set<content_t> unknown_contents;
460 content_t id_counter = 0;
461 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
463 content_t global_id = nodes[i].getContent();
464 content_t id = CONTENT_IGNORE;
466 // Try to find an existing mapping
467 std::map<content_t, content_t>::iterator j = mapping.find(global_id);
468 if(j != mapping.end())
474 // We have to assign a new mapping
476 mapping.insert(std::make_pair(global_id, id));
478 const ContentFeatures &f = nodedef->get(global_id);
479 const std::string &name = f.name;
481 unknown_contents.insert(global_id);
483 nimap->set(id, name);
486 // Update the MapNode
487 nodes[i].setContent(id);
489 for(std::set<content_t>::const_iterator
490 i = unknown_contents.begin();
491 i != unknown_contents.end(); i++){
492 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
493 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
496 // Correct ids in the block to match nodedef based on names.
497 // Unknown ones are added to nodedef.
498 // Will not update itself to match id-name pairs in nodedef.
499 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
502 INodeDefManager *nodedef = gamedef->ndef();
503 // This means the block contains incorrect ids, and we contain
504 // the information to convert those to names.
505 // nodedef contains information to convert our names to globally
507 std::set<content_t> unnamed_contents;
508 std::set<std::string> unallocatable_contents;
509 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
511 content_t local_id = nodes[i].getContent();
513 bool found = nimap->getName(local_id, name);
515 unnamed_contents.insert(local_id);
519 found = nodedef->getId(name, global_id);
521 global_id = gamedef->allocateUnknownNodeId(name);
522 if(global_id == CONTENT_IGNORE){
523 unallocatable_contents.insert(name);
527 nodes[i].setContent(global_id);
529 for(std::set<content_t>::const_iterator
530 i = unnamed_contents.begin();
531 i != unnamed_contents.end(); i++){
532 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
533 <<"Block contains id "<<(*i)
534 <<" with no name mapping"<<std::endl;
536 for(std::set<std::string>::const_iterator
537 i = unallocatable_contents.begin();
538 i != unallocatable_contents.end(); i++){
539 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
540 <<"Could not allocate global id for node name \""
541 <<(*i)<<"\""<<std::endl;
545 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
547 if(!ser_ver_supported(version))
548 throw VersionMismatchException("ERROR: MapBlock format not supported");
552 throw SerializationError("ERROR: Not writing dummy block.");
555 // Can't do this anymore; we have 16-bit dynamically allocated node IDs
556 // in memory; conversion just won't work in this direction.
558 throw SerializationError("MapBlock::serialize: serialization to "
559 "version < 24 not possible");
565 if(getDayNightDiff())
567 if(m_lighting_expired)
569 if(m_generated == false)
577 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
580 MapNode *tmp_nodes = new MapNode[nodecount];
581 for(u32 i=0; i<nodecount; i++)
582 tmp_nodes[i] = data[i];
583 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
585 u8 content_width = 2;
587 writeU8(os, content_width);
588 writeU8(os, params_width);
589 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
590 content_width, params_width, true);
595 u8 content_width = 2;
597 writeU8(os, content_width);
598 writeU8(os, params_width);
599 MapNode::serializeBulk(os, version, data, nodecount,
600 content_width, params_width, true);
606 std::ostringstream oss(std::ios_base::binary);
607 m_node_metadata.serialize(oss);
608 compressZlib(oss.str(), os);
611 Data that goes to disk, but not the network
617 m_node_timers.serialize(os, version);
621 m_static_objects.serialize(os);
624 writeU32(os, getTimestamp());
626 // Write block-specific node definition id mapping
631 m_node_timers.serialize(os, version);
636 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
638 if(!ser_ver_supported(version))
639 throw VersionMismatchException("ERROR: MapBlock format not supported");
641 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
643 m_day_night_differs_expired = false;
647 deSerialize_pre22(is, version, disk);
651 u8 flags = readU8(is);
652 is_underground = (flags & 0x01) ? true : false;
653 m_day_night_differs = (flags & 0x02) ? true : false;
654 m_lighting_expired = (flags & 0x04) ? true : false;
655 m_generated = (flags & 0x08) ? false : true;
660 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
661 <<": Bulk node data"<<std::endl);
662 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
663 u8 content_width = readU8(is);
664 u8 params_width = readU8(is);
665 if(content_width != 1 && content_width != 2)
666 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
667 if(params_width != 2)
668 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
669 MapNode::deSerializeBulk(is, version, data, nodecount,
670 content_width, params_width, true);
675 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
676 <<": Node metadata"<<std::endl);
679 std::ostringstream oss(std::ios_base::binary);
680 decompressZlib(is, oss);
681 std::istringstream iss(oss.str(), std::ios_base::binary);
683 m_node_metadata.deSerialize(iss, m_gamedef);
685 content_nodemeta_deserialize_legacy(iss,
686 &m_node_metadata, &m_node_timers,
689 catch(SerializationError &e)
691 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
692 <<" while deserializing node metadata at ("
693 <<PP(getPos())<<": "<<e.what()<<std::endl;
697 Data that is only on disk
707 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
708 <<": Node timers (ver==24)"<<std::endl);
709 m_node_timers.deSerialize(is, version);
713 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
714 <<": Static objects"<<std::endl);
715 m_static_objects.deSerialize(is);
718 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
719 <<": Timestamp"<<std::endl);
720 setTimestamp(readU32(is));
721 m_disk_timestamp = m_timestamp;
723 // Dynamically re-set ids based on node names
724 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
725 <<": NameIdMapping"<<std::endl);
727 nimap.deSerialize(is);
728 correctBlockNodeIds(&nimap, data, m_gamedef);
731 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
732 <<": Node timers (ver>=25)"<<std::endl);
733 m_node_timers.deSerialize(is, version);
737 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
738 <<": Done."<<std::endl);
745 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
747 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
749 // Initialize default flags
750 is_underground = false;
751 m_day_night_differs = false;
752 m_lighting_expired = false;
755 // Make a temporary buffer
756 u32 ser_length = MapNode::serializedLength(version);
757 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
759 // These have no compression
760 if(version <= 3 || version == 5 || version == 6)
765 throw SerializationError
766 ("MapBlock::deSerialize: no enough input data");
767 is_underground = tmp;
768 is.read((char*)*databuf_nodelist, nodecount * ser_length);
769 if((u32)is.gcount() != nodecount * ser_length)
770 throw SerializationError
771 ("MapBlock::deSerialize: no enough input data");
773 else if(version <= 10)
776 is.read((char*)&t8, 1);
780 // Uncompress and set material data
781 std::ostringstream os(std::ios_base::binary);
782 decompress(is, os, version);
783 std::string s = os.str();
784 if(s.size() != nodecount)
785 throw SerializationError
786 ("MapBlock::deSerialize: invalid format");
787 for(u32 i=0; i<s.size(); i++)
789 databuf_nodelist[i*ser_length] = s[i];
793 // Uncompress and set param data
794 std::ostringstream os(std::ios_base::binary);
795 decompress(is, os, version);
796 std::string s = os.str();
797 if(s.size() != nodecount)
798 throw SerializationError
799 ("MapBlock::deSerialize: invalid format");
800 for(u32 i=0; i<s.size(); i++)
802 databuf_nodelist[i*ser_length + 1] = s[i];
808 // Uncompress and set param2 data
809 std::ostringstream os(std::ios_base::binary);
810 decompress(is, os, version);
811 std::string s = os.str();
812 if(s.size() != nodecount)
813 throw SerializationError
814 ("MapBlock::deSerialize: invalid format");
815 for(u32 i=0; i<s.size(); i++)
817 databuf_nodelist[i*ser_length + 2] = s[i];
821 // All other versions (newest)
825 is.read((char*)&flags, 1);
826 is_underground = (flags & 0x01) ? true : false;
827 m_day_night_differs = (flags & 0x02) ? true : false;
828 m_lighting_expired = (flags & 0x04) ? true : false;
830 m_generated = (flags & 0x08) ? false : true;
833 std::ostringstream os(std::ios_base::binary);
834 decompress(is, os, version);
835 std::string s = os.str();
836 if(s.size() != nodecount*3)
837 throw SerializationError
838 ("MapBlock::deSerialize: decompress resulted in size"
839 " other than nodecount*3");
841 // deserialize nodes from buffer
842 for(u32 i=0; i<nodecount; i++)
844 databuf_nodelist[i*ser_length] = s[i];
845 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
846 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
858 std::string data = deSerializeString(is);
859 std::istringstream iss(data, std::ios_base::binary);
860 content_nodemeta_deserialize_legacy(iss,
861 &m_node_metadata, &m_node_timers,
866 //std::string data = deSerializeLongString(is);
867 std::ostringstream oss(std::ios_base::binary);
868 decompressZlib(is, oss);
869 std::istringstream iss(oss.str(), std::ios_base::binary);
870 content_nodemeta_deserialize_legacy(iss,
871 &m_node_metadata, &m_node_timers,
875 catch(SerializationError &e)
877 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
878 <<" while deserializing node metadata"<<std::endl;
883 // Deserialize node data
884 for(u32 i=0; i<nodecount; i++)
886 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
892 Versions up from 9 have block objects. (DEPRECATED)
895 u16 count = readU16(is);
896 // Not supported and length not known if count is not 0
898 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
899 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
905 Versions up from 15 have static objects.
908 m_static_objects.deSerialize(is);
912 setTimestamp(readU32(is));
913 m_disk_timestamp = m_timestamp;
915 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
918 // Dynamically re-set ids based on node names
920 // If supported, read node definition id mapping
922 nimap.deSerialize(is);
923 // Else set the legacy mapping
925 content_mapnode_get_name_id_mapping(&nimap);
927 correctBlockNodeIds(&nimap, data, m_gamedef);
931 // Legacy data changes
932 // This code has to convert from pre-22 to post-22 format.
933 INodeDefManager *nodedef = m_gamedef->ndef();
934 for(u32 i=0; i<nodecount; i++)
936 const ContentFeatures &f = nodedef->get(data[i].getContent());
938 if(nodedef->getId("default:stone") == data[i].getContent()
939 && data[i].getParam1() == 1)
941 data[i].setContent(nodedef->getId("default:stone_with_coal"));
942 data[i].setParam1(0);
944 else if(nodedef->getId("default:stone") == data[i].getContent()
945 && data[i].getParam1() == 2)
947 data[i].setContent(nodedef->getId("default:stone_with_iron"));
948 data[i].setParam1(0);
951 if(f.legacy_facedir_simple)
953 data[i].setParam2(data[i].getParam1());
954 data[i].setParam1(0);
957 if(f.legacy_wallmounted)
959 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
960 u8 dir_old_format = data[i].getParam2();
961 u8 dir_new_format = 0;
962 for(u8 j=0; j<8; j++)
964 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
970 data[i].setParam2(dir_new_format);
977 Get a quick string to describe what a block actually contains
979 std::string analyze_block(MapBlock *block)
984 std::ostringstream desc;
986 v3s16 p = block->getPos();
988 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
991 switch(block->getModified())
993 case MOD_STATE_CLEAN:
996 case MOD_STATE_WRITE_AT_UNLOAD:
997 desc<<"WRITE_AT_UNLOAD, ";
999 case MOD_STATE_WRITE_NEEDED:
1000 desc<<"WRITE_NEEDED, ";
1003 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1006 if(block->isGenerated())
1007 desc<<"is_gen [X], ";
1009 desc<<"is_gen [ ], ";
1011 if(block->getIsUnderground())
1012 desc<<"is_ug [X], ";
1014 desc<<"is_ug [ ], ";
1016 if(block->getLightingExpired())
1017 desc<<"lighting_exp [X], ";
1019 desc<<"lighting_exp [ ], ";
1021 if(block->isDummy())
1027 bool full_ignore = true;
1028 bool some_ignore = false;
1029 bool full_air = true;
1030 bool some_air = false;
1031 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1032 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1033 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1036 MapNode n = block->getNode(p);
1037 content_t c = n.getContent();
1038 if(c == CONTENT_IGNORE)
1041 full_ignore = false;
1042 if(c == CONTENT_AIR)
1050 std::ostringstream ss;
1053 ss<<"IGNORE (full), ";
1054 else if(some_ignore)
1062 if(ss.str().size()>=2)
1063 desc<<ss.str().substr(0, ss.str().size()-2);
1068 return desc.str().substr(0, desc.str().size()-2);