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"
38 #include "util/basic_macros.h"
40 static const char *modified_reason_strings[] = {
49 "NodeMetaRef::reportMetadataChange",
51 "Timestamp expired (step)",
53 "removeRemovedObjects/remove",
54 "removeRemovedObjects/deactivate",
55 "Stored list cleared in activateObjects due to overflow",
56 "deactivateFarObjects: Static data moved in",
57 "deactivateFarObjects: Static data moved out",
58 "deactivateFarObjects: Static data changed considerably",
59 "finishBlockMake: expireDayNightDiff",
68 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
71 m_pos_relative(pos * MAP_BLOCKSIZE),
73 m_modified(MOD_STATE_WRITE_NEEDED),
74 m_modified_reason(MOD_REASON_INITIAL),
75 is_underground(false),
76 m_lighting_complete(0xFFFF),
77 m_day_night_differs(false),
78 m_day_night_differs_expired(true),
80 m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
81 m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
98 //MutexAutoLock lock(mesh_mutex);
108 bool MapBlock::isValidPositionParent(v3s16 p)
110 if(isValidPosition(p))
115 return m_parent->isValidPosition(getPosRelative() + p);
119 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
121 if (isValidPosition(p) == false)
122 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
125 if (is_valid_position)
126 *is_valid_position = false;
127 return MapNode(CONTENT_IGNORE);
129 if (is_valid_position)
130 *is_valid_position = true;
131 return data[p.Z * zstride + p.Y * ystride + p.X];
134 std::string MapBlock::getModifiedReasonString()
138 const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
139 ARRLEN(modified_reason_strings));
141 for (u32 i = 0; i != ubound; i++) {
142 if ((m_modified_reason & (1 << i)) == 0)
145 reason += modified_reason_strings[i];
149 if (reason.length() > 2)
150 reason.resize(reason.length() - 2);
156 Propagates sunlight down through the block.
157 Doesn't modify nodes that are not affected by sunlight.
159 Returns false if sunlight at bottom block is invalid.
160 Returns true if sunlight at bottom block is valid.
161 Returns true if bottom block doesn't exist.
163 If there is a block above, continues from it.
164 If there is no block above, assumes there is sunlight, unless
165 is_underground is set or highest node is water.
167 All sunlighted nodes are added to light_sources.
169 if remove_light==true, sets non-sunlighted nodes black.
171 if black_air_left!=NULL, it is set to true if non-sunlighted
172 air is left in block.
174 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
175 bool remove_light, bool *black_air_left)
177 INodeDefManager *nodemgr = m_gamedef->ndef();
179 // Whether the sunlight at the top of the bottom block is valid
180 bool block_below_is_valid = true;
182 v3s16 pos_relative = getPosRelative();
184 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
186 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
189 bool no_sunlight = false;
190 //bool no_top_block = false;
192 // Check if node above block has sunlight
194 bool is_valid_position;
195 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
197 if (is_valid_position)
199 if(n.getContent() == CONTENT_IGNORE)
202 no_sunlight = is_underground;
204 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
211 //no_top_block = true;
213 // NOTE: This makes over-ground roofed places sunlighted
214 // Assume sunlight, unless is_underground==true
221 MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
222 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
227 // NOTE: As of now, this just would make everything dark.
229 //no_sunlight = true;
232 #if 0 // Doesn't work; nothing gets light.
233 bool no_sunlight = true;
234 bool no_top_block = false;
235 // Check if node above block has sunlight
237 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
238 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
243 catch(InvalidPositionException &e)
249 /*std::cout<<"("<<x<<","<<z<<"): "
250 <<"no_top_block="<<no_top_block
251 <<", is_underground="<<is_underground
252 <<", no_sunlight="<<no_sunlight
255 s16 y = MAP_BLOCKSIZE-1;
257 // This makes difference to diminishing in water.
258 bool stopped_to_solid_object = false;
260 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
265 MapNode &n = getNodeRef(pos);
267 if(current_light == 0)
271 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
273 // Do nothing: Sunlight is continued
275 else if(nodemgr->get(n).light_propagates == false)
277 // A solid object is on the way.
278 stopped_to_solid_object = true;
286 current_light = diminish_light(current_light);
289 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
291 if(current_light > old_light || remove_light)
293 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
296 if(diminish_light(current_light) != 0)
298 light_sources.insert(pos_relative + pos);
301 if(current_light == 0 && stopped_to_solid_object)
305 *black_air_left = true;
310 // Whether or not the block below should see LIGHT_SUN
311 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
314 If the block below hasn't already been marked invalid:
316 Check if the node below the block has proper sunlight at top.
317 If not, the block below is invalid.
319 Ignore non-transparent nodes as they always have no light
322 if(block_below_is_valid)
324 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
325 if (is_valid_position) {
326 if(nodemgr->get(n).light_propagates)
328 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
329 && sunlight_should_go_down == false)
330 block_below_is_valid = false;
331 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
332 && sunlight_should_go_down == true)
333 block_below_is_valid = false;
338 /*std::cout<<"InvalidBlockException for bottom block node"
340 // Just no block below, no need to panic.
346 return block_below_is_valid;
350 void MapBlock::copyTo(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 data to VoxelManipulator
356 dst.copyFrom(data, data_area, v3s16(0,0,0),
357 getPosRelative(), data_size);
360 void MapBlock::copyFrom(VoxelManipulator &dst)
362 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
363 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
365 // Copy from VoxelManipulator to data
366 dst.copyTo(data, data_area, v3s16(0,0,0),
367 getPosRelative(), data_size);
370 void MapBlock::actuallyUpdateDayNightDiff()
372 INodeDefManager *nodemgr = m_gamedef->ndef();
374 // Running this function un-expires m_day_night_differs
375 m_day_night_differs_expired = false;
378 m_day_night_differs = false;
385 Check if any lighting value differs
387 for (u32 i = 0; i < nodecount; i++) {
388 MapNode &n = data[i];
390 differs = !n.isLightDayNightEq(nodemgr);
396 If some lighting values differ, check if the whole thing is
397 just air. If it is just air, differs = false
400 bool only_air = true;
401 for (u32 i = 0; i < nodecount; i++) {
402 MapNode &n = data[i];
403 if (n.getContent() != CONTENT_AIR) {
412 // Set member variable
413 m_day_night_differs = differs;
416 void MapBlock::expireDayNightDiff()
418 //INodeDefManager *nodemgr = m_gamedef->ndef();
421 m_day_night_differs = false;
422 m_day_night_differs_expired = false;
426 m_day_night_differs_expired = true;
429 s16 MapBlock::getGroundLevel(v2s16 p2d)
435 s16 y = MAP_BLOCKSIZE-1;
438 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
439 if(m_gamedef->ndef()->get(n).walkable)
441 if(y == MAP_BLOCKSIZE-1)
449 catch(InvalidPositionException &e)
458 // List relevant id-name pairs for ids in the block using nodedef
459 // Renumbers the content IDs (starting at 0 and incrementing
460 // use static memory requires about 65535 * sizeof(int) ram in order to be
461 // sure we can handle all content ids. But it's absolutely worth it as it's
462 // a speedup of 4 for one of the major time consuming functions on storing
464 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
465 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
466 INodeDefManager *nodedef)
468 memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
470 std::set<content_t> unknown_contents;
471 content_t id_counter = 0;
472 for (u32 i = 0; i < MapBlock::nodecount; i++) {
473 content_t global_id = nodes[i].getContent();
474 content_t id = CONTENT_IGNORE;
476 // Try to find an existing mapping
477 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
478 id = getBlockNodeIdMapping_mapping[global_id];
482 // We have to assign a new mapping
484 getBlockNodeIdMapping_mapping[global_id] = id;
486 const ContentFeatures &f = nodedef->get(global_id);
487 const std::string &name = f.name;
489 unknown_contents.insert(global_id);
491 nimap->set(id, name);
494 // Update the MapNode
495 nodes[i].setContent(id);
497 for(std::set<content_t>::const_iterator
498 i = unknown_contents.begin();
499 i != unknown_contents.end(); ++i){
500 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
501 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
504 // Correct ids in the block to match nodedef based on names.
505 // Unknown ones are added to nodedef.
506 // Will not update itself to match id-name pairs in nodedef.
507 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
510 INodeDefManager *nodedef = gamedef->ndef();
511 // This means the block contains incorrect ids, and we contain
512 // the information to convert those to names.
513 // nodedef contains information to convert our names to globally
515 std::set<content_t> unnamed_contents;
516 std::set<std::string> unallocatable_contents;
517 for (u32 i = 0; i < MapBlock::nodecount; i++) {
518 content_t local_id = nodes[i].getContent();
520 bool found = nimap->getName(local_id, name);
522 unnamed_contents.insert(local_id);
526 found = nodedef->getId(name, global_id);
528 global_id = gamedef->allocateUnknownNodeId(name);
529 if(global_id == CONTENT_IGNORE){
530 unallocatable_contents.insert(name);
534 nodes[i].setContent(global_id);
536 for(std::set<content_t>::const_iterator
537 i = unnamed_contents.begin();
538 i != unnamed_contents.end(); ++i){
539 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
540 <<"Block contains id "<<(*i)
541 <<" with no name mapping"<<std::endl;
543 for(std::set<std::string>::const_iterator
544 i = unallocatable_contents.begin();
545 i != unallocatable_contents.end(); ++i){
546 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
547 <<"Could not allocate global id for node name \""
548 <<(*i)<<"\""<<std::endl;
552 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
554 if(!ser_ver_supported(version))
555 throw VersionMismatchException("ERROR: MapBlock format not supported");
559 throw SerializationError("ERROR: Not writing dummy block.");
562 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
568 if(getDayNightDiff())
570 if(m_generated == false)
574 writeU16(os, m_lighting_complete);
583 MapNode *tmp_nodes = new MapNode[nodecount];
584 for(u32 i=0; i<nodecount; i++)
585 tmp_nodes[i] = data[i];
586 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
588 u8 content_width = 2;
590 writeU8(os, content_width);
591 writeU8(os, params_width);
592 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
593 content_width, params_width, true);
598 u8 content_width = 2;
600 writeU8(os, content_width);
601 writeU8(os, params_width);
602 MapNode::serializeBulk(os, version, data, nodecount,
603 content_width, params_width, true);
609 std::ostringstream oss(std::ios_base::binary);
610 m_node_metadata.serialize(oss, version, disk);
611 compressZlib(oss.str(), os);
614 Data that goes to disk, but not the network
620 m_node_timers.serialize(os, version);
624 m_static_objects.serialize(os);
627 writeU32(os, getTimestamp());
629 // Write block-specific node definition id mapping
634 m_node_timers.serialize(os, version);
639 void MapBlock::serializeNetworkSpecific(std::ostream &os)
642 throw SerializationError("ERROR: Not writing dummy block.");
645 writeU8(os, 1); // version
646 writeF1000(os, 0); // deprecated heat
647 writeF1000(os, 0); // deprecated humidity
650 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
652 if(!ser_ver_supported(version))
653 throw VersionMismatchException("ERROR: MapBlock format not supported");
655 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
657 m_day_night_differs_expired = false;
661 deSerialize_pre22(is, version, disk);
665 u8 flags = readU8(is);
666 is_underground = (flags & 0x01) ? true : false;
667 m_day_night_differs = (flags & 0x02) ? true : false;
669 m_lighting_complete = 0xFFFF;
671 m_lighting_complete = readU16(is);
672 m_generated = (flags & 0x08) ? false : true;
677 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
678 <<": Bulk node data"<<std::endl);
679 u8 content_width = readU8(is);
680 u8 params_width = readU8(is);
681 if(content_width != 1 && content_width != 2)
682 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
683 if(params_width != 2)
684 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
685 MapNode::deSerializeBulk(is, version, data, nodecount,
686 content_width, params_width, true);
691 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
692 <<": Node metadata"<<std::endl);
695 std::ostringstream oss(std::ios_base::binary);
696 decompressZlib(is, oss);
697 std::istringstream iss(oss.str(), std::ios_base::binary);
699 m_node_metadata.deSerialize(iss, m_gamedef->idef());
701 content_nodemeta_deserialize_legacy(iss,
702 &m_node_metadata, &m_node_timers,
704 } catch(SerializationError &e) {
705 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
706 <<" while deserializing node metadata at ("
707 <<PP(getPos())<<": "<<e.what()<<std::endl;
711 Data that is only on disk
721 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
722 <<": Node timers (ver==24)"<<std::endl);
723 m_node_timers.deSerialize(is, version);
727 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
728 <<": Static objects"<<std::endl);
729 m_static_objects.deSerialize(is);
732 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
733 <<": Timestamp"<<std::endl);
734 setTimestamp(readU32(is));
735 m_disk_timestamp = m_timestamp;
737 // Dynamically re-set ids based on node names
738 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
739 <<": NameIdMapping"<<std::endl);
741 nimap.deSerialize(is);
742 correctBlockNodeIds(&nimap, data, m_gamedef);
745 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
746 <<": Node timers (ver>=25)"<<std::endl);
747 m_node_timers.deSerialize(is, version);
751 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
752 <<": Done."<<std::endl);
755 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
758 int version = readU8(is);
760 // throw SerializationError("unsupported MapBlock version");
762 readF1000(is); // deprecated heat
763 readF1000(is); // deprecated humidity
766 catch(SerializationError &e)
768 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
769 <<": "<<e.what()<<std::endl;
777 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
779 // Initialize default flags
780 is_underground = false;
781 m_day_night_differs = false;
782 m_lighting_complete = 0xFFFF;
785 // Make a temporary buffer
786 u32 ser_length = MapNode::serializedLength(version);
787 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
789 // These have no compression
790 if (version <= 3 || version == 5 || version == 6) {
793 if (is.gcount() != 1)
794 throw SerializationError(std::string(FUNCTION_NAME)
795 + ": not enough input data");
796 is_underground = tmp;
797 is.read((char *)*databuf_nodelist, nodecount * ser_length);
798 if ((u32)is.gcount() != nodecount * ser_length)
799 throw SerializationError(std::string(FUNCTION_NAME)
800 + ": not enough input data");
801 } else if (version <= 10) {
803 is.read((char *)&t8, 1);
807 // Uncompress and set material data
808 std::ostringstream os(std::ios_base::binary);
809 decompress(is, os, version);
810 std::string s = os.str();
811 if (s.size() != nodecount)
812 throw SerializationError(std::string(FUNCTION_NAME)
813 + ": not enough input data");
814 for (u32 i = 0; i < s.size(); i++) {
815 databuf_nodelist[i*ser_length] = s[i];
819 // Uncompress and set param data
820 std::ostringstream os(std::ios_base::binary);
821 decompress(is, os, version);
822 std::string s = os.str();
823 if (s.size() != nodecount)
824 throw SerializationError(std::string(FUNCTION_NAME)
825 + ": not enough input data");
826 for (u32 i = 0; i < s.size(); i++) {
827 databuf_nodelist[i*ser_length + 1] = s[i];
832 // Uncompress and set param2 data
833 std::ostringstream os(std::ios_base::binary);
834 decompress(is, os, version);
835 std::string s = os.str();
836 if (s.size() != nodecount)
837 throw SerializationError(std::string(FUNCTION_NAME)
838 + ": not enough input data");
839 for (u32 i = 0; i < s.size(); i++) {
840 databuf_nodelist[i*ser_length + 2] = s[i];
843 } else { // All other versions (10 to 21)
845 is.read((char*)&flags, 1);
846 is_underground = (flags & 0x01) ? true : false;
847 m_day_night_differs = (flags & 0x02) ? true : false;
849 m_generated = (flags & 0x08) ? false : true;
852 std::ostringstream os(std::ios_base::binary);
853 decompress(is, os, version);
854 std::string s = os.str();
855 if (s.size() != nodecount * 3)
856 throw SerializationError(std::string(FUNCTION_NAME)
857 + ": decompress resulted in size other than nodecount*3");
859 // deserialize nodes from buffer
860 for (u32 i = 0; i < nodecount; i++) {
861 databuf_nodelist[i*ser_length] = s[i];
862 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
863 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
873 std::string data = deSerializeString(is);
874 std::istringstream iss(data, std::ios_base::binary);
875 content_nodemeta_deserialize_legacy(iss,
876 &m_node_metadata, &m_node_timers,
879 //std::string data = deSerializeLongString(is);
880 std::ostringstream oss(std::ios_base::binary);
881 decompressZlib(is, oss);
882 std::istringstream iss(oss.str(), std::ios_base::binary);
883 content_nodemeta_deserialize_legacy(iss,
884 &m_node_metadata, &m_node_timers,
887 } catch(SerializationError &e) {
888 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
889 <<" while deserializing node metadata"<<std::endl;
894 // Deserialize node data
895 for (u32 i = 0; i < nodecount; i++) {
896 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
901 Versions up from 9 have block objects. (DEPRECATED)
904 u16 count = readU16(is);
905 // Not supported and length not known if count is not 0
907 warningstream<<"MapBlock::deSerialize_pre22(): "
908 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
914 Versions up from 15 have static objects.
917 m_static_objects.deSerialize(is);
921 setTimestamp(readU32(is));
922 m_disk_timestamp = m_timestamp;
924 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
927 // Dynamically re-set ids based on node names
929 // If supported, read node definition id mapping
931 nimap.deSerialize(is);
932 // Else set the legacy mapping
934 content_mapnode_get_name_id_mapping(&nimap);
936 correctBlockNodeIds(&nimap, data, m_gamedef);
940 // Legacy data changes
941 // This code has to convert from pre-22 to post-22 format.
942 INodeDefManager *nodedef = m_gamedef->ndef();
943 for(u32 i=0; i<nodecount; i++)
945 const ContentFeatures &f = nodedef->get(data[i].getContent());
947 if(nodedef->getId("default:stone") == data[i].getContent()
948 && data[i].getParam1() == 1)
950 data[i].setContent(nodedef->getId("default:stone_with_coal"));
951 data[i].setParam1(0);
953 else if(nodedef->getId("default:stone") == data[i].getContent()
954 && data[i].getParam1() == 2)
956 data[i].setContent(nodedef->getId("default:stone_with_iron"));
957 data[i].setParam1(0);
960 if(f.legacy_facedir_simple)
962 data[i].setParam2(data[i].getParam1());
963 data[i].setParam1(0);
966 if(f.legacy_wallmounted)
968 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
969 u8 dir_old_format = data[i].getParam2();
970 u8 dir_new_format = 0;
971 for(u8 j=0; j<8; j++)
973 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
979 data[i].setParam2(dir_new_format);
986 Get a quick string to describe what a block actually contains
988 std::string analyze_block(MapBlock *block)
993 std::ostringstream desc;
995 v3s16 p = block->getPos();
997 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
1000 switch(block->getModified())
1002 case MOD_STATE_CLEAN:
1005 case MOD_STATE_WRITE_AT_UNLOAD:
1006 desc<<"WRITE_AT_UNLOAD, ";
1008 case MOD_STATE_WRITE_NEEDED:
1009 desc<<"WRITE_NEEDED, ";
1012 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1015 if(block->isGenerated())
1016 desc<<"is_gen [X], ";
1018 desc<<"is_gen [ ], ";
1020 if(block->getIsUnderground())
1021 desc<<"is_ug [X], ";
1023 desc<<"is_ug [ ], ";
1025 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1027 if(block->isDummy())
1033 bool full_ignore = true;
1034 bool some_ignore = false;
1035 bool full_air = true;
1036 bool some_air = false;
1037 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1038 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1039 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1042 MapNode n = block->getNodeNoEx(p);
1043 content_t c = n.getContent();
1044 if(c == CONTENT_IGNORE)
1047 full_ignore = false;
1048 if(c == CONTENT_AIR)
1056 std::ostringstream ss;
1059 ss<<"IGNORE (full), ";
1060 else if(some_ignore)
1068 if(ss.str().size()>=2)
1069 desc<<ss.str().substr(0, ss.str().size()-2);
1074 return desc.str().substr(0, desc.str().size()-2);