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"
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, bool *is_valid_position)
102 if (isValidPosition(p) == false)
103 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
106 if (is_valid_position)
107 *is_valid_position = false;
108 return MapNode(CONTENT_IGNORE);
110 if (is_valid_position)
111 *is_valid_position = true;
112 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
116 Propagates sunlight down through the block.
117 Doesn't modify nodes that are not affected by sunlight.
119 Returns false if sunlight at bottom block is invalid.
120 Returns true if sunlight at bottom block is valid.
121 Returns true if bottom block doesn't exist.
123 If there is a block above, continues from it.
124 If there is no block above, assumes there is sunlight, unless
125 is_underground is set or highest node is water.
127 All sunlighted nodes are added to light_sources.
129 if remove_light==true, sets non-sunlighted nodes black.
131 if black_air_left!=NULL, it is set to true if non-sunlighted
132 air is left in block.
134 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
135 bool remove_light, bool *black_air_left)
137 INodeDefManager *nodemgr = m_gamedef->ndef();
139 // Whether the sunlight at the top of the bottom block is valid
140 bool block_below_is_valid = true;
142 v3s16 pos_relative = getPosRelative();
144 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
146 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
149 bool no_sunlight = false;
150 //bool no_top_block = false;
152 // Check if node above block has sunlight
154 bool is_valid_position;
155 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
157 if (is_valid_position)
159 if(n.getContent() == CONTENT_IGNORE)
162 no_sunlight = is_underground;
164 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
171 //no_top_block = true;
173 // NOTE: This makes over-ground roofed places sunlighted
174 // Assume sunlight, unless is_underground==true
181 MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
182 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
187 // NOTE: As of now, this just would make everything dark.
189 //no_sunlight = true;
192 #if 0 // Doesn't work; nothing gets light.
193 bool no_sunlight = true;
194 bool no_top_block = false;
195 // Check if node above block has sunlight
197 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
198 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
203 catch(InvalidPositionException &e)
209 /*std::cout<<"("<<x<<","<<z<<"): "
210 <<"no_top_block="<<no_top_block
211 <<", is_underground="<<is_underground
212 <<", no_sunlight="<<no_sunlight
215 s16 y = MAP_BLOCKSIZE-1;
217 // This makes difference to diminishing in water.
218 bool stopped_to_solid_object = false;
220 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
225 MapNode &n = getNodeRef(pos);
227 if(current_light == 0)
231 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
233 // Do nothing: Sunlight is continued
235 else if(nodemgr->get(n).light_propagates == false)
237 // A solid object is on the way.
238 stopped_to_solid_object = true;
246 current_light = diminish_light(current_light);
249 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
251 if(current_light > old_light || remove_light)
253 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
256 if(diminish_light(current_light) != 0)
258 light_sources.insert(pos_relative + pos);
261 if(current_light == 0 && stopped_to_solid_object)
265 *black_air_left = true;
270 // Whether or not the block below should see LIGHT_SUN
271 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
274 If the block below hasn't already been marked invalid:
276 Check if the node below the block has proper sunlight at top.
277 If not, the block below is invalid.
279 Ignore non-transparent nodes as they always have no light
282 if(block_below_is_valid)
284 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
285 if (is_valid_position) {
286 if(nodemgr->get(n).light_propagates)
288 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
289 && sunlight_should_go_down == false)
290 block_below_is_valid = false;
291 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
292 && sunlight_should_go_down == true)
293 block_below_is_valid = false;
298 /*std::cout<<"InvalidBlockException for bottom block node"
300 // Just no block below, no need to panic.
306 return block_below_is_valid;
310 void MapBlock::copyTo(VoxelManipulator &dst)
312 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
313 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
315 // Copy from data to VoxelManipulator
316 dst.copyFrom(data, data_area, v3s16(0,0,0),
317 getPosRelative(), data_size);
320 void MapBlock::copyFrom(VoxelManipulator &dst)
322 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
323 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
325 // Copy from VoxelManipulator to data
326 dst.copyTo(data, data_area, v3s16(0,0,0),
327 getPosRelative(), data_size);
330 void MapBlock::actuallyUpdateDayNightDiff()
332 INodeDefManager *nodemgr = m_gamedef->ndef();
333 // Running this function un-expires m_day_night_differs
334 m_day_night_differs_expired = false;
338 m_day_night_differs = false;
342 bool differs = false;
345 Check if any lighting value differs
347 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
349 MapNode &n = data[i];
350 if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr))
358 If some lighting values differ, check if the whole thing is
359 just air. If it is, differ = false
363 bool only_air = true;
364 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
366 MapNode &n = data[i];
367 if(n.getContent() != CONTENT_AIR)
377 // Set member variable
378 m_day_night_differs = differs;
381 void MapBlock::expireDayNightDiff()
383 //INodeDefManager *nodemgr = m_gamedef->ndef();
386 m_day_night_differs = false;
387 m_day_night_differs_expired = false;
391 m_day_night_differs_expired = true;
394 s16 MapBlock::getGroundLevel(v2s16 p2d)
400 s16 y = MAP_BLOCKSIZE-1;
403 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
404 if(m_gamedef->ndef()->get(n).walkable)
406 if(y == MAP_BLOCKSIZE-1)
414 catch(InvalidPositionException &e)
423 // List relevant id-name pairs for ids in the block using nodedef
424 // Renumbers the content IDs (starting at 0 and incrementing
425 // use static memory requires about 65535 * sizeof(int) ram in order to be
426 // sure we can handle all content ids. But it's absolutely worth it as it's
427 // a speedup of 4 for one of the major time consuming functions on storing
429 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX];
430 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
431 INodeDefManager *nodedef)
433 memset(getBlockNodeIdMapping_mapping, 0xFF, USHRT_MAX * sizeof(content_t));
435 std::set<content_t> unknown_contents;
436 content_t id_counter = 0;
437 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
439 content_t global_id = nodes[i].getContent();
440 content_t id = CONTENT_IGNORE;
442 // Try to find an existing mapping
443 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
444 id = getBlockNodeIdMapping_mapping[global_id];
448 // We have to assign a new mapping
450 getBlockNodeIdMapping_mapping[global_id] = id;
452 const ContentFeatures &f = nodedef->get(global_id);
453 const std::string &name = f.name;
455 unknown_contents.insert(global_id);
457 nimap->set(id, name);
460 // Update the MapNode
461 nodes[i].setContent(id);
463 for(std::set<content_t>::const_iterator
464 i = unknown_contents.begin();
465 i != unknown_contents.end(); i++){
466 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
467 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
470 // Correct ids in the block to match nodedef based on names.
471 // Unknown ones are added to nodedef.
472 // Will not update itself to match id-name pairs in nodedef.
473 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
476 INodeDefManager *nodedef = gamedef->ndef();
477 // This means the block contains incorrect ids, and we contain
478 // the information to convert those to names.
479 // nodedef contains information to convert our names to globally
481 std::set<content_t> unnamed_contents;
482 std::set<std::string> unallocatable_contents;
483 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
485 content_t local_id = nodes[i].getContent();
487 bool found = nimap->getName(local_id, name);
489 unnamed_contents.insert(local_id);
493 found = nodedef->getId(name, global_id);
495 global_id = gamedef->allocateUnknownNodeId(name);
496 if(global_id == CONTENT_IGNORE){
497 unallocatable_contents.insert(name);
501 nodes[i].setContent(global_id);
503 for(std::set<content_t>::const_iterator
504 i = unnamed_contents.begin();
505 i != unnamed_contents.end(); i++){
506 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
507 <<"Block contains id "<<(*i)
508 <<" with no name mapping"<<std::endl;
510 for(std::set<std::string>::const_iterator
511 i = unallocatable_contents.begin();
512 i != unallocatable_contents.end(); i++){
513 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
514 <<"Could not allocate global id for node name \""
515 <<(*i)<<"\""<<std::endl;
519 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
521 if(!ser_ver_supported(version))
522 throw VersionMismatchException("ERROR: MapBlock format not supported");
526 throw SerializationError("ERROR: Not writing dummy block.");
529 assert(version >= SER_FMT_CLIENT_VER_LOWEST);
535 if(getDayNightDiff())
537 if(m_lighting_expired)
539 if(m_generated == false)
547 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
550 MapNode *tmp_nodes = new MapNode[nodecount];
551 for(u32 i=0; i<nodecount; i++)
552 tmp_nodes[i] = data[i];
553 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
555 u8 content_width = 2;
557 writeU8(os, content_width);
558 writeU8(os, params_width);
559 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
560 content_width, params_width, true);
565 u8 content_width = 2;
567 writeU8(os, content_width);
568 writeU8(os, params_width);
569 MapNode::serializeBulk(os, version, data, nodecount,
570 content_width, params_width, true);
576 std::ostringstream oss(std::ios_base::binary);
577 m_node_metadata.serialize(oss);
578 compressZlib(oss.str(), os);
581 Data that goes to disk, but not the network
587 m_node_timers.serialize(os, version);
591 m_static_objects.serialize(os);
594 writeU32(os, getTimestamp());
596 // Write block-specific node definition id mapping
601 m_node_timers.serialize(os, version);
606 void MapBlock::serializeNetworkSpecific(std::ostream &os, u16 net_proto_version)
610 throw SerializationError("ERROR: Not writing dummy block.");
613 if(net_proto_version >= 21){
615 writeU8(os, version);
616 writeF1000(os, 0); // deprecated heat
617 writeF1000(os, 0); // deprecated humidity
621 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
623 if(!ser_ver_supported(version))
624 throw VersionMismatchException("ERROR: MapBlock format not supported");
626 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
628 m_day_night_differs_expired = false;
632 deSerialize_pre22(is, version, disk);
636 u8 flags = readU8(is);
637 is_underground = (flags & 0x01) ? true : false;
638 m_day_night_differs = (flags & 0x02) ? true : false;
639 m_lighting_expired = (flags & 0x04) ? true : false;
640 m_generated = (flags & 0x08) ? false : true;
645 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
646 <<": Bulk node data"<<std::endl);
647 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
648 u8 content_width = readU8(is);
649 u8 params_width = readU8(is);
650 if(content_width != 1 && content_width != 2)
651 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
652 if(params_width != 2)
653 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
654 MapNode::deSerializeBulk(is, version, data, nodecount,
655 content_width, params_width, true);
660 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
661 <<": Node metadata"<<std::endl);
664 std::ostringstream oss(std::ios_base::binary);
665 decompressZlib(is, oss);
666 std::istringstream iss(oss.str(), std::ios_base::binary);
668 m_node_metadata.deSerialize(iss, m_gamedef);
670 content_nodemeta_deserialize_legacy(iss,
671 &m_node_metadata, &m_node_timers,
674 catch(SerializationError &e)
676 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
677 <<" while deserializing node metadata at ("
678 <<PP(getPos())<<": "<<e.what()<<std::endl;
682 Data that is only on disk
692 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
693 <<": Node timers (ver==24)"<<std::endl);
694 m_node_timers.deSerialize(is, version);
698 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
699 <<": Static objects"<<std::endl);
700 m_static_objects.deSerialize(is);
703 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
704 <<": Timestamp"<<std::endl);
705 setTimestamp(readU32(is));
706 m_disk_timestamp = m_timestamp;
708 // Dynamically re-set ids based on node names
709 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
710 <<": NameIdMapping"<<std::endl);
712 nimap.deSerialize(is);
713 correctBlockNodeIds(&nimap, data, m_gamedef);
716 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
717 <<": Node timers (ver>=25)"<<std::endl);
718 m_node_timers.deSerialize(is, version);
722 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
723 <<": Done."<<std::endl);
726 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
729 int version = readU8(is);
731 // throw SerializationError("unsupported MapBlock version");
733 readF1000(is); // deprecated heat
734 readF1000(is); // deprecated humidity
737 catch(SerializationError &e)
739 errorstream<<"WARNING: MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
740 <<": "<<e.what()<<std::endl;
748 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
750 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
752 // Initialize default flags
753 is_underground = false;
754 m_day_night_differs = false;
755 m_lighting_expired = false;
758 // Make a temporary buffer
759 u32 ser_length = MapNode::serializedLength(version);
760 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
762 // These have no compression
763 if(version <= 3 || version == 5 || version == 6)
768 throw SerializationError
769 ("MapBlock::deSerialize: no enough input data");
770 is_underground = tmp;
771 is.read((char*)*databuf_nodelist, nodecount * ser_length);
772 if((u32)is.gcount() != nodecount * ser_length)
773 throw SerializationError
774 ("MapBlock::deSerialize: no enough input data");
776 else if(version <= 10)
779 is.read((char*)&t8, 1);
783 // Uncompress and set material data
784 std::ostringstream os(std::ios_base::binary);
785 decompress(is, os, version);
786 std::string s = os.str();
787 if(s.size() != nodecount)
788 throw SerializationError
789 ("MapBlock::deSerialize: invalid format");
790 for(u32 i=0; i<s.size(); i++)
792 databuf_nodelist[i*ser_length] = s[i];
796 // Uncompress and set param 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 + 1] = s[i];
811 // Uncompress and set param2 data
812 std::ostringstream os(std::ios_base::binary);
813 decompress(is, os, version);
814 std::string s = os.str();
815 if(s.size() != nodecount)
816 throw SerializationError
817 ("MapBlock::deSerialize: invalid format");
818 for(u32 i=0; i<s.size(); i++)
820 databuf_nodelist[i*ser_length + 2] = s[i];
824 // All other versions (newest)
828 is.read((char*)&flags, 1);
829 is_underground = (flags & 0x01) ? true : false;
830 m_day_night_differs = (flags & 0x02) ? true : false;
831 m_lighting_expired = (flags & 0x04) ? true : false;
833 m_generated = (flags & 0x08) ? false : true;
836 std::ostringstream os(std::ios_base::binary);
837 decompress(is, os, version);
838 std::string s = os.str();
839 if(s.size() != nodecount*3)
840 throw SerializationError
841 ("MapBlock::deSerialize: decompress resulted in size"
842 " other than nodecount*3");
844 // deserialize nodes from buffer
845 for(u32 i=0; i<nodecount; i++)
847 databuf_nodelist[i*ser_length] = s[i];
848 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
849 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
861 std::string data = deSerializeString(is);
862 std::istringstream iss(data, std::ios_base::binary);
863 content_nodemeta_deserialize_legacy(iss,
864 &m_node_metadata, &m_node_timers,
869 //std::string data = deSerializeLongString(is);
870 std::ostringstream oss(std::ios_base::binary);
871 decompressZlib(is, oss);
872 std::istringstream iss(oss.str(), std::ios_base::binary);
873 content_nodemeta_deserialize_legacy(iss,
874 &m_node_metadata, &m_node_timers,
878 catch(SerializationError &e)
880 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
881 <<" while deserializing node metadata"<<std::endl;
886 // Deserialize node data
887 for(u32 i=0; i<nodecount; i++)
889 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
895 Versions up from 9 have block objects. (DEPRECATED)
898 u16 count = readU16(is);
899 // Not supported and length not known if count is not 0
901 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
902 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
908 Versions up from 15 have static objects.
911 m_static_objects.deSerialize(is);
915 setTimestamp(readU32(is));
916 m_disk_timestamp = m_timestamp;
918 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
921 // Dynamically re-set ids based on node names
923 // If supported, read node definition id mapping
925 nimap.deSerialize(is);
926 // Else set the legacy mapping
928 content_mapnode_get_name_id_mapping(&nimap);
930 correctBlockNodeIds(&nimap, data, m_gamedef);
934 // Legacy data changes
935 // This code has to convert from pre-22 to post-22 format.
936 INodeDefManager *nodedef = m_gamedef->ndef();
937 for(u32 i=0; i<nodecount; i++)
939 const ContentFeatures &f = nodedef->get(data[i].getContent());
941 if(nodedef->getId("default:stone") == data[i].getContent()
942 && data[i].getParam1() == 1)
944 data[i].setContent(nodedef->getId("default:stone_with_coal"));
945 data[i].setParam1(0);
947 else if(nodedef->getId("default:stone") == data[i].getContent()
948 && data[i].getParam1() == 2)
950 data[i].setContent(nodedef->getId("default:stone_with_iron"));
951 data[i].setParam1(0);
954 if(f.legacy_facedir_simple)
956 data[i].setParam2(data[i].getParam1());
957 data[i].setParam1(0);
960 if(f.legacy_wallmounted)
962 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
963 u8 dir_old_format = data[i].getParam2();
964 u8 dir_new_format = 0;
965 for(u8 j=0; j<8; j++)
967 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
973 data[i].setParam2(dir_new_format);
980 Get a quick string to describe what a block actually contains
982 std::string analyze_block(MapBlock *block)
987 std::ostringstream desc;
989 v3s16 p = block->getPos();
991 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
994 switch(block->getModified())
996 case MOD_STATE_CLEAN:
999 case MOD_STATE_WRITE_AT_UNLOAD:
1000 desc<<"WRITE_AT_UNLOAD, ";
1002 case MOD_STATE_WRITE_NEEDED:
1003 desc<<"WRITE_NEEDED, ";
1006 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1009 if(block->isGenerated())
1010 desc<<"is_gen [X], ";
1012 desc<<"is_gen [ ], ";
1014 if(block->getIsUnderground())
1015 desc<<"is_ug [X], ";
1017 desc<<"is_ug [ ], ";
1019 if(block->getLightingExpired())
1020 desc<<"lighting_exp [X], ";
1022 desc<<"lighting_exp [ ], ";
1024 if(block->isDummy())
1030 bool full_ignore = true;
1031 bool some_ignore = false;
1032 bool full_air = true;
1033 bool some_air = false;
1034 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1035 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1036 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1039 MapNode n = block->getNodeNoEx(p);
1040 content_t c = n.getContent();
1041 if(c == CONTENT_IGNORE)
1044 full_ignore = false;
1045 if(c == CONTENT_AIR)
1053 std::ostringstream ss;
1056 ss<<"IGNORE (full), ";
1057 else if(some_ignore)
1065 if(ss.str().size()>=2)
1066 desc<<ss.str().substr(0, ss.str().size()-2);
1071 return desc.str().substr(0, desc.str().size()-2);