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),
90 bool MapBlock::isValidPositionParent(v3s16 p)
92 if(isValidPosition(p))
97 return m_parent->isValidPosition(getPosRelative() + p);
101 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
103 if (isValidPosition(p) == false)
104 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
107 if (is_valid_position)
108 *is_valid_position = false;
109 return MapNode(CONTENT_IGNORE);
111 if (is_valid_position)
112 *is_valid_position = true;
113 return data[p.Z * zstride + p.Y * ystride + p.X];
116 std::string MapBlock::getModifiedReasonString()
120 const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
121 ARRLEN(modified_reason_strings));
123 for (u32 i = 0; i != ubound; i++) {
124 if ((m_modified_reason & (1 << i)) == 0)
127 reason += modified_reason_strings[i];
131 if (reason.length() > 2)
132 reason.resize(reason.length() - 2);
138 Propagates sunlight down through the block.
139 Doesn't modify nodes that are not affected by sunlight.
141 Returns false if sunlight at bottom block is invalid.
142 Returns true if sunlight at bottom block is valid.
143 Returns true if bottom block doesn't exist.
145 If there is a block above, continues from it.
146 If there is no block above, assumes there is sunlight, unless
147 is_underground is set or highest node is water.
149 All sunlighted nodes are added to light_sources.
151 if remove_light==true, sets non-sunlighted nodes black.
153 if black_air_left!=NULL, it is set to true if non-sunlighted
154 air is left in block.
156 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
157 bool remove_light, bool *black_air_left)
159 INodeDefManager *nodemgr = m_gamedef->ndef();
161 // Whether the sunlight at the top of the bottom block is valid
162 bool block_below_is_valid = true;
164 v3s16 pos_relative = getPosRelative();
166 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
168 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
171 bool no_sunlight = false;
172 //bool no_top_block = false;
174 // Check if node above block has sunlight
176 bool is_valid_position;
177 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
179 if (is_valid_position)
181 if(n.getContent() == CONTENT_IGNORE)
184 no_sunlight = is_underground;
186 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
193 //no_top_block = true;
195 // NOTE: This makes over-ground roofed places sunlighted
196 // Assume sunlight, unless is_underground==true
203 MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
204 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
209 // NOTE: As of now, this just would make everything dark.
211 //no_sunlight = true;
214 #if 0 // Doesn't work; nothing gets light.
215 bool no_sunlight = true;
216 bool no_top_block = false;
217 // Check if node above block has sunlight
219 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
220 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
225 catch(InvalidPositionException &e)
231 /*std::cout<<"("<<x<<","<<z<<"): "
232 <<"no_top_block="<<no_top_block
233 <<", is_underground="<<is_underground
234 <<", no_sunlight="<<no_sunlight
237 s16 y = MAP_BLOCKSIZE-1;
239 // This makes difference to diminishing in water.
240 bool stopped_to_solid_object = false;
242 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
247 MapNode &n = getNodeRef(pos);
249 if(current_light == 0)
253 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
255 // Do nothing: Sunlight is continued
257 else if(nodemgr->get(n).light_propagates == false)
259 // A solid object is on the way.
260 stopped_to_solid_object = true;
268 current_light = diminish_light(current_light);
271 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
273 if(current_light > old_light || remove_light)
275 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
278 if(diminish_light(current_light) != 0)
280 light_sources.insert(pos_relative + pos);
283 if(current_light == 0 && stopped_to_solid_object)
287 *black_air_left = true;
292 // Whether or not the block below should see LIGHT_SUN
293 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
296 If the block below hasn't already been marked invalid:
298 Check if the node below the block has proper sunlight at top.
299 If not, the block below is invalid.
301 Ignore non-transparent nodes as they always have no light
304 if(block_below_is_valid)
306 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
307 if (is_valid_position) {
308 if(nodemgr->get(n).light_propagates)
310 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
311 && sunlight_should_go_down == false)
312 block_below_is_valid = false;
313 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
314 && sunlight_should_go_down == true)
315 block_below_is_valid = false;
320 /*std::cout<<"InvalidBlockException for bottom block node"
322 // Just no block below, no need to panic.
328 return block_below_is_valid;
332 void MapBlock::copyTo(VoxelManipulator &dst)
334 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
335 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
337 // Copy from data to VoxelManipulator
338 dst.copyFrom(data, data_area, v3s16(0,0,0),
339 getPosRelative(), data_size);
342 void MapBlock::copyFrom(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 VoxelManipulator to data
348 dst.copyTo(data, data_area, v3s16(0,0,0),
349 getPosRelative(), data_size);
352 void MapBlock::actuallyUpdateDayNightDiff()
354 INodeDefManager *nodemgr = m_gamedef->ndef();
356 // Running this function un-expires m_day_night_differs
357 m_day_night_differs_expired = false;
360 m_day_night_differs = false;
364 bool differs = false;
367 Check if any lighting value differs
370 MapNode previous_n(CONTENT_IGNORE);
371 for (u32 i = 0; i < nodecount; i++) {
374 // If node is identical to previous node, don't verify if it differs
378 differs = !n.isLightDayNightEq(nodemgr);
385 If some lighting values differ, check if the whole thing is
386 just air. If it is just air, differs = false
389 bool only_air = true;
390 for (u32 i = 0; i < nodecount; i++) {
391 MapNode &n = data[i];
392 if (n.getContent() != CONTENT_AIR) {
401 // Set member variable
402 m_day_night_differs = differs;
405 void MapBlock::expireDayNightDiff()
408 m_day_night_differs = false;
409 m_day_night_differs_expired = false;
413 m_day_night_differs_expired = true;
416 s16 MapBlock::getGroundLevel(v2s16 p2d)
422 s16 y = MAP_BLOCKSIZE-1;
425 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
426 if(m_gamedef->ndef()->get(n).walkable)
428 if(y == MAP_BLOCKSIZE-1)
436 catch(InvalidPositionException &e)
445 // List relevant id-name pairs for ids in the block using nodedef
446 // Renumbers the content IDs (starting at 0 and incrementing
447 // use static memory requires about 65535 * sizeof(int) ram in order to be
448 // sure we can handle all content ids. But it's absolutely worth it as it's
449 // a speedup of 4 for one of the major time consuming functions on storing
451 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
452 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
453 INodeDefManager *nodedef)
455 memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
457 std::set<content_t> unknown_contents;
458 content_t id_counter = 0;
459 for (u32 i = 0; i < MapBlock::nodecount; i++) {
460 content_t global_id = nodes[i].getContent();
461 content_t id = CONTENT_IGNORE;
463 // Try to find an existing mapping
464 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
465 id = getBlockNodeIdMapping_mapping[global_id];
469 // We have to assign a new mapping
471 getBlockNodeIdMapping_mapping[global_id] = id;
473 const ContentFeatures &f = nodedef->get(global_id);
474 const std::string &name = f.name;
476 unknown_contents.insert(global_id);
478 nimap->set(id, name);
481 // Update the MapNode
482 nodes[i].setContent(id);
484 for(std::set<content_t>::const_iterator
485 i = unknown_contents.begin();
486 i != unknown_contents.end(); ++i){
487 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
488 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
491 // Correct ids in the block to match nodedef based on names.
492 // Unknown ones are added to nodedef.
493 // Will not update itself to match id-name pairs in nodedef.
494 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
497 INodeDefManager *nodedef = gamedef->ndef();
498 // This means the block contains incorrect ids, and we contain
499 // the information to convert those to names.
500 // nodedef contains information to convert our names to globally
502 std::unordered_set<content_t> unnamed_contents;
503 std::unordered_set<std::string> unallocatable_contents;
505 bool previous_exists = false;
506 content_t previous_local_id = CONTENT_IGNORE;
507 content_t previous_global_id = CONTENT_IGNORE;
509 for (u32 i = 0; i < MapBlock::nodecount; i++) {
510 content_t local_id = nodes[i].getContent();
511 // If previous node local_id was found and same than before, don't lookup maps
512 // apply directly previous resolved id
513 // This permits to massively improve loading performance when nodes are similar
514 // example: default:air, default:stone are massively present
515 if (previous_exists && local_id == previous_local_id) {
516 nodes[i].setContent(previous_global_id);
521 if (!nimap->getName(local_id, name)) {
522 unnamed_contents.insert(local_id);
523 previous_exists = false;
528 if (!nodedef->getId(name, global_id)) {
529 global_id = gamedef->allocateUnknownNodeId(name);
530 if (global_id == CONTENT_IGNORE) {
531 unallocatable_contents.insert(name);
532 previous_exists = false;
536 nodes[i].setContent(global_id);
538 // Save previous node local_id & global_id result
539 previous_local_id = local_id;
540 previous_global_id = global_id;
541 previous_exists = true;
544 for (const content_t c: unnamed_contents) {
545 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
546 << "Block contains id " << c
547 << " with no name mapping" << std::endl;
549 for (const std::string &node_name: unallocatable_contents) {
550 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
551 << "Could not allocate global id for node name \""
552 << node_name << "\"" << std::endl;
556 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
558 if(!ser_ver_supported(version))
559 throw VersionMismatchException("ERROR: MapBlock format not supported");
562 throw SerializationError("ERROR: Not writing dummy block.");
564 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
570 if(getDayNightDiff())
572 if(m_generated == false)
576 writeU16(os, m_lighting_complete);
585 MapNode *tmp_nodes = new MapNode[nodecount];
586 for(u32 i=0; i<nodecount; i++)
587 tmp_nodes[i] = data[i];
588 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
590 u8 content_width = 2;
592 writeU8(os, content_width);
593 writeU8(os, params_width);
594 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
595 content_width, params_width, true);
600 u8 content_width = 2;
602 writeU8(os, content_width);
603 writeU8(os, params_width);
604 MapNode::serializeBulk(os, version, data, nodecount,
605 content_width, params_width, true);
611 std::ostringstream oss(std::ios_base::binary);
612 m_node_metadata.serialize(oss, version, disk);
613 compressZlib(oss.str(), os);
616 Data that goes to disk, but not the network
622 m_node_timers.serialize(os, version);
626 m_static_objects.serialize(os);
629 writeU32(os, getTimestamp());
631 // Write block-specific node definition id mapping
636 m_node_timers.serialize(os, version);
641 void MapBlock::serializeNetworkSpecific(std::ostream &os)
644 throw SerializationError("ERROR: Not writing dummy block.");
647 writeU8(os, 1); // version
648 writeF1000(os, 0); // deprecated heat
649 writeF1000(os, 0); // deprecated humidity
652 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
654 if(!ser_ver_supported(version))
655 throw VersionMismatchException("ERROR: MapBlock format not supported");
657 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
659 m_day_night_differs_expired = false;
663 deSerialize_pre22(is, version, disk);
667 u8 flags = readU8(is);
668 is_underground = (flags & 0x01) ? true : false;
669 m_day_night_differs = (flags & 0x02) ? true : false;
671 m_lighting_complete = 0xFFFF;
673 m_lighting_complete = readU16(is);
674 m_generated = (flags & 0x08) ? false : true;
679 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
680 <<": Bulk node data"<<std::endl);
681 u8 content_width = readU8(is);
682 u8 params_width = readU8(is);
683 if(content_width != 1 && content_width != 2)
684 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
685 if(params_width != 2)
686 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
687 MapNode::deSerializeBulk(is, version, data, nodecount,
688 content_width, params_width, true);
693 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
694 <<": Node metadata"<<std::endl);
697 std::ostringstream oss(std::ios_base::binary);
698 decompressZlib(is, oss);
699 std::istringstream iss(oss.str(), std::ios_base::binary);
701 m_node_metadata.deSerialize(iss, m_gamedef->idef());
703 content_nodemeta_deserialize_legacy(iss,
704 &m_node_metadata, &m_node_timers,
706 } catch(SerializationError &e) {
707 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
708 <<" while deserializing node metadata at ("
709 <<PP(getPos())<<": "<<e.what()<<std::endl;
713 Data that is only on disk
723 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
724 <<": Node timers (ver==24)"<<std::endl);
725 m_node_timers.deSerialize(is, version);
729 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
730 <<": Static objects"<<std::endl);
731 m_static_objects.deSerialize(is);
734 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
735 <<": Timestamp"<<std::endl);
736 setTimestamp(readU32(is));
737 m_disk_timestamp = m_timestamp;
739 // Dynamically re-set ids based on node names
740 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
741 <<": NameIdMapping"<<std::endl);
743 nimap.deSerialize(is);
744 correctBlockNodeIds(&nimap, data, m_gamedef);
747 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
748 <<": Node timers (ver>=25)"<<std::endl);
749 m_node_timers.deSerialize(is, version);
753 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
754 <<": Done."<<std::endl);
757 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
760 int version = readU8(is);
762 // throw SerializationError("unsupported MapBlock version");
764 readF1000(is); // deprecated heat
765 readF1000(is); // deprecated humidity
768 catch(SerializationError &e)
770 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
771 <<": "<<e.what()<<std::endl;
779 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
781 // Initialize default flags
782 is_underground = false;
783 m_day_night_differs = false;
784 m_lighting_complete = 0xFFFF;
787 // Make a temporary buffer
788 u32 ser_length = MapNode::serializedLength(version);
789 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
791 // These have no compression
792 if (version <= 3 || version == 5 || version == 6) {
795 if (is.gcount() != 1)
796 throw SerializationError(std::string(FUNCTION_NAME)
797 + ": not enough input data");
798 is_underground = tmp;
799 is.read((char *)*databuf_nodelist, nodecount * ser_length);
800 if ((u32)is.gcount() != nodecount * ser_length)
801 throw SerializationError(std::string(FUNCTION_NAME)
802 + ": not enough input data");
803 } else if (version <= 10) {
805 is.read((char *)&t8, 1);
809 // Uncompress and set material 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(std::string(FUNCTION_NAME)
815 + ": not enough input data");
816 for (u32 i = 0; i < s.size(); i++) {
817 databuf_nodelist[i*ser_length] = s[i];
821 // Uncompress and set param data
822 std::ostringstream os(std::ios_base::binary);
823 decompress(is, os, version);
824 std::string s = os.str();
825 if (s.size() != nodecount)
826 throw SerializationError(std::string(FUNCTION_NAME)
827 + ": not enough input data");
828 for (u32 i = 0; i < s.size(); i++) {
829 databuf_nodelist[i*ser_length + 1] = s[i];
834 // Uncompress and set param2 data
835 std::ostringstream os(std::ios_base::binary);
836 decompress(is, os, version);
837 std::string s = os.str();
838 if (s.size() != nodecount)
839 throw SerializationError(std::string(FUNCTION_NAME)
840 + ": not enough input data");
841 for (u32 i = 0; i < s.size(); i++) {
842 databuf_nodelist[i*ser_length + 2] = s[i];
845 } else { // All other versions (10 to 21)
847 is.read((char*)&flags, 1);
848 is_underground = (flags & 0x01) ? true : false;
849 m_day_night_differs = (flags & 0x02) ? true : false;
851 m_generated = (flags & 0x08) ? false : true;
854 std::ostringstream os(std::ios_base::binary);
855 decompress(is, os, version);
856 std::string s = os.str();
857 if (s.size() != nodecount * 3)
858 throw SerializationError(std::string(FUNCTION_NAME)
859 + ": decompress resulted in size other than nodecount*3");
861 // deserialize nodes from buffer
862 for (u32 i = 0; i < nodecount; i++) {
863 databuf_nodelist[i*ser_length] = s[i];
864 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
865 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
875 std::string data = deSerializeString(is);
876 std::istringstream iss(data, std::ios_base::binary);
877 content_nodemeta_deserialize_legacy(iss,
878 &m_node_metadata, &m_node_timers,
881 //std::string data = deSerializeLongString(is);
882 std::ostringstream oss(std::ios_base::binary);
883 decompressZlib(is, oss);
884 std::istringstream iss(oss.str(), std::ios_base::binary);
885 content_nodemeta_deserialize_legacy(iss,
886 &m_node_metadata, &m_node_timers,
889 } catch(SerializationError &e) {
890 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
891 <<" while deserializing node metadata"<<std::endl;
896 // Deserialize node data
897 for (u32 i = 0; i < nodecount; i++) {
898 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
903 Versions up from 9 have block objects. (DEPRECATED)
906 u16 count = readU16(is);
907 // Not supported and length not known if count is not 0
909 warningstream<<"MapBlock::deSerialize_pre22(): "
910 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
916 Versions up from 15 have static objects.
919 m_static_objects.deSerialize(is);
923 setTimestamp(readU32(is));
924 m_disk_timestamp = m_timestamp;
926 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
929 // Dynamically re-set ids based on node names
931 // If supported, read node definition id mapping
933 nimap.deSerialize(is);
934 // Else set the legacy mapping
936 content_mapnode_get_name_id_mapping(&nimap);
938 correctBlockNodeIds(&nimap, data, m_gamedef);
942 // Legacy data changes
943 // This code has to convert from pre-22 to post-22 format.
944 INodeDefManager *nodedef = m_gamedef->ndef();
945 for(u32 i=0; i<nodecount; i++)
947 const ContentFeatures &f = nodedef->get(data[i].getContent());
949 if(nodedef->getId("default:stone") == data[i].getContent()
950 && data[i].getParam1() == 1)
952 data[i].setContent(nodedef->getId("default:stone_with_coal"));
953 data[i].setParam1(0);
955 else if(nodedef->getId("default:stone") == data[i].getContent()
956 && data[i].getParam1() == 2)
958 data[i].setContent(nodedef->getId("default:stone_with_iron"));
959 data[i].setParam1(0);
962 if(f.legacy_facedir_simple)
964 data[i].setParam2(data[i].getParam1());
965 data[i].setParam1(0);
968 if(f.legacy_wallmounted)
970 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
971 u8 dir_old_format = data[i].getParam2();
972 u8 dir_new_format = 0;
973 for(u8 j=0; j<8; j++)
975 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
981 data[i].setParam2(dir_new_format);
988 Get a quick string to describe what a block actually contains
990 std::string analyze_block(MapBlock *block)
995 std::ostringstream desc;
997 v3s16 p = block->getPos();
999 snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
1002 switch(block->getModified())
1004 case MOD_STATE_CLEAN:
1007 case MOD_STATE_WRITE_AT_UNLOAD:
1008 desc<<"WRITE_AT_UNLOAD, ";
1010 case MOD_STATE_WRITE_NEEDED:
1011 desc<<"WRITE_NEEDED, ";
1014 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1017 if(block->isGenerated())
1018 desc<<"is_gen [X], ";
1020 desc<<"is_gen [ ], ";
1022 if(block->getIsUnderground())
1023 desc<<"is_ug [X], ";
1025 desc<<"is_ug [ ], ";
1027 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1029 if(block->isDummy())
1035 bool full_ignore = true;
1036 bool some_ignore = false;
1037 bool full_air = true;
1038 bool some_air = false;
1039 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1040 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1041 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1044 MapNode n = block->getNodeNoEx(p);
1045 content_t c = n.getContent();
1046 if(c == CONTENT_IGNORE)
1049 full_ignore = false;
1050 if(c == CONTENT_AIR)
1058 std::ostringstream ss;
1061 ss<<"IGNORE (full), ";
1062 else if(some_ignore)
1070 if(ss.str().size()>=2)
1071 desc<<ss.str().substr(0, ss.str().size()-2);
1076 return desc.str().substr(0, desc.str().size()-2);