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),
81 //JMutexAutoLock lock(mesh_mutex);
95 bool MapBlock::isValidPositionParent(v3s16 p)
97 if(isValidPosition(p))
102 return m_parent->isValidPosition(getPosRelative() + p);
106 MapNode MapBlock::getNodeParent(v3s16 p)
108 if(isValidPosition(p) == false)
110 return m_parent->getNode(getPosRelative() + p);
115 throw InvalidPositionException();
116 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
120 void MapBlock::setNodeParent(v3s16 p, MapNode & n)
122 if(isValidPosition(p) == false)
124 m_parent->setNode(getPosRelative() + p, n);
129 throw InvalidPositionException();
130 data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X] = n;
134 MapNode MapBlock::getNodeParentNoEx(v3s16 p)
136 if(isValidPosition(p) == false)
139 return m_parent->getNode(getPosRelative() + p);
141 catch(InvalidPositionException &e)
143 return MapNode(CONTENT_IGNORE);
150 return MapNode(CONTENT_IGNORE);
152 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
157 Propagates sunlight down through the block.
158 Doesn't modify nodes that are not affected by sunlight.
160 Returns false if sunlight at bottom block is invalid.
161 Returns true if sunlight at bottom block is valid.
162 Returns true if bottom block doesn't exist.
164 If there is a block above, continues from it.
165 If there is no block above, assumes there is sunlight, unless
166 is_underground is set or highest node is water.
168 All sunlighted nodes are added to light_sources.
170 if remove_light==true, sets non-sunlighted nodes black.
172 if black_air_left!=NULL, it is set to true if non-sunlighted
173 air is left in block.
175 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
176 bool remove_light, bool *black_air_left)
178 INodeDefManager *nodemgr = m_gamedef->ndef();
180 // Whether the sunlight at the top of the bottom block is valid
181 bool block_below_is_valid = true;
183 v3s16 pos_relative = getPosRelative();
185 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
187 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
190 bool no_sunlight = false;
191 bool no_top_block = false;
192 // Check if node above block has sunlight
194 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
195 if(n.getContent() == CONTENT_IGNORE)
198 no_sunlight = is_underground;
200 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
205 catch(InvalidPositionException &e)
209 // NOTE: This makes over-ground roofed places sunlighted
210 // Assume sunlight, unless is_underground==true
217 MapNode n = getNode(v3s16(x, MAP_BLOCKSIZE-1, z));
218 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
223 // NOTE: As of now, this just would make everything dark.
225 //no_sunlight = true;
228 #if 0 // Doesn't work; nothing gets light.
229 bool no_sunlight = true;
230 bool no_top_block = false;
231 // Check if node above block has sunlight
233 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
234 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
239 catch(InvalidPositionException &e)
245 /*std::cout<<"("<<x<<","<<z<<"): "
246 <<"no_top_block="<<no_top_block
247 <<", is_underground="<<is_underground
248 <<", no_sunlight="<<no_sunlight
251 s16 y = MAP_BLOCKSIZE-1;
253 // This makes difference to diminishing in water.
254 bool stopped_to_solid_object = false;
256 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
261 MapNode &n = getNodeRef(pos);
263 if(current_light == 0)
267 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
269 // Do nothing: Sunlight is continued
271 else if(nodemgr->get(n).light_propagates == false)
273 // A solid object is on the way.
274 stopped_to_solid_object = true;
282 current_light = diminish_light(current_light);
285 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
287 if(current_light > old_light || remove_light)
289 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
292 if(diminish_light(current_light) != 0)
294 light_sources.insert(pos_relative + pos);
297 if(current_light == 0 && stopped_to_solid_object)
301 *black_air_left = true;
306 // Whether or not the block below should see LIGHT_SUN
307 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
310 If the block below hasn't already been marked invalid:
312 Check if the node below the block has proper sunlight at top.
313 If not, the block below is invalid.
315 Ignore non-transparent nodes as they always have no light
319 if(block_below_is_valid)
321 MapNode n = getNodeParent(v3s16(x, -1, z));
322 if(nodemgr->get(n).light_propagates)
324 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
325 && sunlight_should_go_down == false)
326 block_below_is_valid = false;
327 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
328 && sunlight_should_go_down == true)
329 block_below_is_valid = false;
333 catch(InvalidPositionException &e)
335 /*std::cout<<"InvalidBlockException for bottom block node"
337 // Just no block below, no need to panic.
342 return block_below_is_valid;
346 void MapBlock::copyTo(VoxelManipulator &dst)
348 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
349 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
351 // Copy from data to VoxelManipulator
352 dst.copyFrom(data, data_area, v3s16(0,0,0),
353 getPosRelative(), data_size);
356 void MapBlock::copyFrom(VoxelManipulator &dst)
358 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
359 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
361 // Copy from VoxelManipulator to data
362 dst.copyTo(data, data_area, v3s16(0,0,0),
363 getPosRelative(), data_size);
366 void MapBlock::actuallyUpdateDayNightDiff()
368 INodeDefManager *nodemgr = m_gamedef->ndef();
369 // Running this function un-expires m_day_night_differs
370 m_day_night_differs_expired = false;
374 m_day_night_differs = false;
378 bool differs = false;
381 Check if any lighting value differs
383 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
385 MapNode &n = data[i];
386 if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr))
394 If some lighting values differ, check if the whole thing is
395 just air. If it is, differ = false
399 bool only_air = true;
400 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
402 MapNode &n = data[i];
403 if(n.getContent() != CONTENT_AIR)
413 // Set member variable
414 m_day_night_differs = differs;
417 void MapBlock::expireDayNightDiff()
419 //INodeDefManager *nodemgr = m_gamedef->ndef();
422 m_day_night_differs = false;
423 m_day_night_differs_expired = false;
427 m_day_night_differs_expired = true;
430 s16 MapBlock::getGroundLevel(v2s16 p2d)
436 s16 y = MAP_BLOCKSIZE-1;
439 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
440 if(m_gamedef->ndef()->get(n).walkable)
442 if(y == MAP_BLOCKSIZE-1)
450 catch(InvalidPositionException &e)
459 // List relevant id-name pairs for ids in the block using nodedef
460 // Renumbers the content IDs (starting at 0 and incrementing
461 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
462 INodeDefManager *nodedef)
464 std::map<content_t, content_t> mapping;
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 std::map<content_t, content_t>::iterator j = mapping.find(global_id);
474 if(j != mapping.end())
480 // We have to assign a new mapping
482 mapping.insert(std::make_pair(global_id, id));
484 const ContentFeatures &f = nodedef->get(global_id);
485 const std::string &name = f.name;
487 unknown_contents.insert(global_id);
489 nimap->set(id, name);
492 // Update the MapNode
493 nodes[i].setContent(id);
495 for(std::set<content_t>::const_iterator
496 i = unknown_contents.begin();
497 i != unknown_contents.end(); i++){
498 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
499 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
502 // Correct ids in the block to match nodedef based on names.
503 // Unknown ones are added to nodedef.
504 // Will not update itself to match id-name pairs in nodedef.
505 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
508 INodeDefManager *nodedef = gamedef->ndef();
509 // This means the block contains incorrect ids, and we contain
510 // the information to convert those to names.
511 // nodedef contains information to convert our names to globally
513 std::set<content_t> unnamed_contents;
514 std::set<std::string> unallocatable_contents;
515 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
517 content_t local_id = nodes[i].getContent();
519 bool found = nimap->getName(local_id, name);
521 unnamed_contents.insert(local_id);
525 found = nodedef->getId(name, global_id);
527 global_id = gamedef->allocateUnknownNodeId(name);
528 if(global_id == CONTENT_IGNORE){
529 unallocatable_contents.insert(name);
533 nodes[i].setContent(global_id);
535 for(std::set<content_t>::const_iterator
536 i = unnamed_contents.begin();
537 i != unnamed_contents.end(); i++){
538 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
539 <<"Block contains id "<<(*i)
540 <<" with no name mapping"<<std::endl;
542 for(std::set<std::string>::const_iterator
543 i = unallocatable_contents.begin();
544 i != unallocatable_contents.end(); i++){
545 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
546 <<"Could not allocate global id for node name \""
547 <<(*i)<<"\""<<std::endl;
551 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
553 if(!ser_ver_supported(version))
554 throw VersionMismatchException("ERROR: MapBlock format not supported");
558 throw SerializationError("ERROR: Not writing dummy block.");
561 // Can't do this anymore; we have 16-bit dynamically allocated node IDs
562 // in memory; conversion just won't work in this direction.
564 throw SerializationError("MapBlock::serialize: serialization to "
565 "version < 24 not possible");
571 if(getDayNightDiff())
573 if(m_lighting_expired)
575 if(m_generated == false)
583 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
586 MapNode *tmp_nodes = new MapNode[nodecount];
587 for(u32 i=0; i<nodecount; i++)
588 tmp_nodes[i] = data[i];
589 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
591 u8 content_width = 2;
593 writeU8(os, content_width);
594 writeU8(os, params_width);
595 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
596 content_width, params_width, true);
601 u8 content_width = 2;
603 writeU8(os, content_width);
604 writeU8(os, params_width);
605 MapNode::serializeBulk(os, version, data, nodecount,
606 content_width, params_width, true);
612 std::ostringstream oss(std::ios_base::binary);
613 m_node_metadata.serialize(oss);
614 compressZlib(oss.str(), os);
617 Data that goes to disk, but not the network
623 m_node_timers.serialize(os, version);
627 m_static_objects.serialize(os);
630 writeU32(os, getTimestamp());
632 // Write block-specific node definition id mapping
637 m_node_timers.serialize(os, version);
641 writeF1000(os, heat);
642 writeF1000(os, humidity);
647 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
649 if(!ser_ver_supported(version))
650 throw VersionMismatchException("ERROR: MapBlock format not supported");
652 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
654 m_day_night_differs_expired = false;
658 deSerialize_pre22(is, version, disk);
662 u8 flags = readU8(is);
663 is_underground = (flags & 0x01) ? true : false;
664 m_day_night_differs = (flags & 0x02) ? true : false;
665 m_lighting_expired = (flags & 0x04) ? true : false;
666 m_generated = (flags & 0x08) ? false : true;
671 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
672 <<": Bulk node data"<<std::endl);
673 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
674 u8 content_width = readU8(is);
675 u8 params_width = readU8(is);
676 if(content_width != 1 && content_width != 2)
677 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
678 if(params_width != 2)
679 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
680 MapNode::deSerializeBulk(is, version, data, nodecount,
681 content_width, params_width, true);
686 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
687 <<": Node metadata"<<std::endl);
690 std::ostringstream oss(std::ios_base::binary);
691 decompressZlib(is, oss);
692 std::istringstream iss(oss.str(), std::ios_base::binary);
694 m_node_metadata.deSerialize(iss, m_gamedef);
696 content_nodemeta_deserialize_legacy(iss,
697 &m_node_metadata, &m_node_timers,
700 catch(SerializationError &e)
702 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
703 <<" while deserializing node metadata at ("
704 <<PP(getPos())<<": "<<e.what()<<std::endl;
708 Data that is only on disk
718 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
719 <<": Node timers (ver==24)"<<std::endl);
720 m_node_timers.deSerialize(is, version);
724 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
725 <<": Static objects"<<std::endl);
726 m_static_objects.deSerialize(is);
729 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
730 <<": Timestamp"<<std::endl);
731 setTimestamp(readU32(is));
732 m_disk_timestamp = m_timestamp;
734 // Dynamically re-set ids based on node names
735 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
736 <<": NameIdMapping"<<std::endl);
738 nimap.deSerialize(is);
739 correctBlockNodeIds(&nimap, data, m_gamedef);
742 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
743 <<": Node timers (ver>=25)"<<std::endl);
744 m_node_timers.deSerialize(is, version);
748 heat = readF1000(is);
749 humidity = readF1000(is);
753 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
754 <<": Done."<<std::endl);
761 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
763 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
765 // Initialize default flags
766 is_underground = false;
767 m_day_night_differs = false;
768 m_lighting_expired = false;
771 // Make a temporary buffer
772 u32 ser_length = MapNode::serializedLength(version);
773 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
775 // These have no compression
776 if(version <= 3 || version == 5 || version == 6)
781 throw SerializationError
782 ("MapBlock::deSerialize: no enough input data");
783 is_underground = tmp;
784 is.read((char*)*databuf_nodelist, nodecount * ser_length);
785 if((u32)is.gcount() != nodecount * ser_length)
786 throw SerializationError
787 ("MapBlock::deSerialize: no enough input data");
789 else if(version <= 10)
792 is.read((char*)&t8, 1);
796 // Uncompress and set material data
797 std::ostringstream os(std::ios_base::binary);
798 decompress(is, os, version);
799 std::string s = os.str();
800 if(s.size() != nodecount)
801 throw SerializationError
802 ("MapBlock::deSerialize: invalid format");
803 for(u32 i=0; i<s.size(); i++)
805 databuf_nodelist[i*ser_length] = s[i];
809 // Uncompress and set param 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 + 1] = s[i];
824 // Uncompress and set param2 data
825 std::ostringstream os(std::ios_base::binary);
826 decompress(is, os, version);
827 std::string s = os.str();
828 if(s.size() != nodecount)
829 throw SerializationError
830 ("MapBlock::deSerialize: invalid format");
831 for(u32 i=0; i<s.size(); i++)
833 databuf_nodelist[i*ser_length + 2] = s[i];
837 // All other versions (newest)
841 is.read((char*)&flags, 1);
842 is_underground = (flags & 0x01) ? true : false;
843 m_day_night_differs = (flags & 0x02) ? true : false;
844 m_lighting_expired = (flags & 0x04) ? true : false;
846 m_generated = (flags & 0x08) ? false : true;
849 std::ostringstream os(std::ios_base::binary);
850 decompress(is, os, version);
851 std::string s = os.str();
852 if(s.size() != nodecount*3)
853 throw SerializationError
854 ("MapBlock::deSerialize: decompress resulted in size"
855 " other than nodecount*3");
857 // deserialize nodes from buffer
858 for(u32 i=0; i<nodecount; i++)
860 databuf_nodelist[i*ser_length] = s[i];
861 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
862 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
874 std::string data = deSerializeString(is);
875 std::istringstream iss(data, std::ios_base::binary);
876 content_nodemeta_deserialize_legacy(iss,
877 &m_node_metadata, &m_node_timers,
882 //std::string data = deSerializeLongString(is);
883 std::ostringstream oss(std::ios_base::binary);
884 decompressZlib(is, oss);
885 std::istringstream iss(oss.str(), std::ios_base::binary);
886 content_nodemeta_deserialize_legacy(iss,
887 &m_node_metadata, &m_node_timers,
891 catch(SerializationError &e)
893 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
894 <<" while deserializing node metadata"<<std::endl;
899 // Deserialize node data
900 for(u32 i=0; i<nodecount; i++)
902 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
908 Versions up from 9 have block objects. (DEPRECATED)
911 u16 count = readU16(is);
912 // Not supported and length not known if count is not 0
914 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
915 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
921 Versions up from 15 have static objects.
924 m_static_objects.deSerialize(is);
928 setTimestamp(readU32(is));
929 m_disk_timestamp = m_timestamp;
931 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
934 // Dynamically re-set ids based on node names
936 // If supported, read node definition id mapping
938 nimap.deSerialize(is);
939 // Else set the legacy mapping
941 content_mapnode_get_name_id_mapping(&nimap);
943 correctBlockNodeIds(&nimap, data, m_gamedef);
947 // Legacy data changes
948 // This code has to convert from pre-22 to post-22 format.
949 INodeDefManager *nodedef = m_gamedef->ndef();
950 for(u32 i=0; i<nodecount; i++)
952 const ContentFeatures &f = nodedef->get(data[i].getContent());
954 if(nodedef->getId("default:stone") == data[i].getContent()
955 && data[i].getParam1() == 1)
957 data[i].setContent(nodedef->getId("default:stone_with_coal"));
958 data[i].setParam1(0);
960 else if(nodedef->getId("default:stone") == data[i].getContent()
961 && data[i].getParam1() == 2)
963 data[i].setContent(nodedef->getId("default:stone_with_iron"));
964 data[i].setParam1(0);
967 if(f.legacy_facedir_simple)
969 data[i].setParam2(data[i].getParam1());
970 data[i].setParam1(0);
973 if(f.legacy_wallmounted)
975 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
976 u8 dir_old_format = data[i].getParam2();
977 u8 dir_new_format = 0;
978 for(u8 j=0; j<8; j++)
980 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
986 data[i].setParam2(dir_new_format);
993 Get a quick string to describe what a block actually contains
995 std::string analyze_block(MapBlock *block)
1000 std::ostringstream desc;
1002 v3s16 p = block->getPos();
1004 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
1007 switch(block->getModified())
1009 case MOD_STATE_CLEAN:
1012 case MOD_STATE_WRITE_AT_UNLOAD:
1013 desc<<"WRITE_AT_UNLOAD, ";
1015 case MOD_STATE_WRITE_NEEDED:
1016 desc<<"WRITE_NEEDED, ";
1019 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1022 if(block->isGenerated())
1023 desc<<"is_gen [X], ";
1025 desc<<"is_gen [ ], ";
1027 if(block->getIsUnderground())
1028 desc<<"is_ug [X], ";
1030 desc<<"is_ug [ ], ";
1032 if(block->getLightingExpired())
1033 desc<<"lighting_exp [X], ";
1035 desc<<"lighting_exp [ ], ";
1037 if(block->isDummy())
1043 bool full_ignore = true;
1044 bool some_ignore = false;
1045 bool full_air = true;
1046 bool some_air = false;
1047 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1048 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1049 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1052 MapNode n = block->getNode(p);
1053 content_t c = n.getContent();
1054 if(c == CONTENT_IGNORE)
1057 full_ignore = false;
1058 if(c == CONTENT_AIR)
1066 std::ostringstream ss;
1069 ss<<"IGNORE (full), ";
1070 else if(some_ignore)
1078 if(ss.str().size()>=2)
1079 desc<<ss.str().substr(0, ss.str().size()-2);
1084 return desc.str().substr(0, desc.str().size()-2);