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)) {
96 return m_parent->isValidPosition(getPosRelative() + p);
99 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
101 if (!isValidPosition(p))
102 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
105 if (is_valid_position)
106 *is_valid_position = false;
107 return {CONTENT_IGNORE};
109 if (is_valid_position)
110 *is_valid_position = true;
111 return data[p.Z * zstride + p.Y * ystride + p.X];
114 std::string MapBlock::getModifiedReasonString()
118 const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
119 ARRLEN(modified_reason_strings));
121 for (u32 i = 0; i != ubound; i++) {
122 if ((m_modified_reason & (1 << i)) == 0)
125 reason += modified_reason_strings[i];
129 if (reason.length() > 2)
130 reason.resize(reason.length() - 2);
136 Propagates sunlight down through the block.
137 Doesn't modify nodes that are not affected by sunlight.
139 Returns false if sunlight at bottom block is invalid.
140 Returns true if sunlight at bottom block is valid.
141 Returns true if bottom block doesn't exist.
143 If there is a block above, continues from it.
144 If there is no block above, assumes there is sunlight, unless
145 is_underground is set or highest node is water.
147 All sunlighted nodes are added to light_sources.
149 if remove_light==true, sets non-sunlighted nodes black.
151 if black_air_left!=NULL, it is set to true if non-sunlighted
152 air is left in block.
154 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
155 bool remove_light, bool *black_air_left)
157 INodeDefManager *nodemgr = m_gamedef->ndef();
159 // Whether the sunlight at the top of the bottom block is valid
160 bool block_below_is_valid = true;
162 v3s16 pos_relative = getPosRelative();
164 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
166 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
169 bool no_sunlight = false;
170 //bool no_top_block = false;
172 // Check if node above block has sunlight
174 bool is_valid_position;
175 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
177 if (is_valid_position)
179 if(n.getContent() == CONTENT_IGNORE)
182 no_sunlight = is_underground;
184 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
191 //no_top_block = true;
193 // NOTE: This makes over-ground roofed places sunlighted
194 // Assume sunlight, unless is_underground==true
201 MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
202 if (!m_gamedef->ndef()->get(n).sunlight_propagates) {
206 // NOTE: As of now, this just would make everything dark.
208 //no_sunlight = true;
211 #if 0 // Doesn't work; nothing gets light.
212 bool no_sunlight = true;
213 bool no_top_block = false;
214 // Check if node above block has sunlight
216 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
217 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
222 catch(InvalidPositionException &e)
228 /*std::cout<<"("<<x<<","<<z<<"): "
229 <<"no_top_block="<<no_top_block
230 <<", is_underground="<<is_underground
231 <<", no_sunlight="<<no_sunlight
234 s16 y = MAP_BLOCKSIZE-1;
236 // This makes difference to diminishing in water.
237 bool stopped_to_solid_object = false;
239 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
244 MapNode &n = getNodeRef(pos);
246 if(current_light == 0)
250 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
252 // Do nothing: Sunlight is continued
253 } else if (!nodemgr->get(n).light_propagates) {
254 // A solid object is on the way.
255 stopped_to_solid_object = true;
263 current_light = diminish_light(current_light);
266 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
268 if(current_light > old_light || remove_light)
270 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
273 if(diminish_light(current_light) != 0)
275 light_sources.insert(pos_relative + pos);
278 if(current_light == 0 && stopped_to_solid_object)
282 *black_air_left = true;
287 // Whether or not the block below should see LIGHT_SUN
288 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
291 If the block below hasn't already been marked invalid:
293 Check if the node below the block has proper sunlight at top.
294 If not, the block below is invalid.
296 Ignore non-transparent nodes as they always have no light
299 if(block_below_is_valid)
301 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
302 if (is_valid_position) {
303 if(nodemgr->get(n).light_propagates)
305 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
306 && !sunlight_should_go_down)
307 block_below_is_valid = false;
308 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
309 && sunlight_should_go_down)
310 block_below_is_valid = false;
315 /*std::cout<<"InvalidBlockException for bottom block node"
317 // Just no block below, no need to panic.
323 return block_below_is_valid;
327 void MapBlock::copyTo(VoxelManipulator &dst)
329 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
330 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
332 // Copy from data to VoxelManipulator
333 dst.copyFrom(data, data_area, v3s16(0,0,0),
334 getPosRelative(), data_size);
337 void MapBlock::copyFrom(VoxelManipulator &dst)
339 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
340 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
342 // Copy from VoxelManipulator to data
343 dst.copyTo(data, data_area, v3s16(0,0,0),
344 getPosRelative(), data_size);
347 void MapBlock::actuallyUpdateDayNightDiff()
349 INodeDefManager *nodemgr = m_gamedef->ndef();
351 // Running this function un-expires m_day_night_differs
352 m_day_night_differs_expired = false;
355 m_day_night_differs = false;
359 bool differs = false;
362 Check if any lighting value differs
365 MapNode previous_n(CONTENT_IGNORE);
366 for (u32 i = 0; i < nodecount; i++) {
369 // If node is identical to previous node, don't verify if it differs
373 differs = !n.isLightDayNightEq(nodemgr);
380 If some lighting values differ, check if the whole thing is
381 just air. If it is just air, differs = false
384 bool only_air = true;
385 for (u32 i = 0; i < nodecount; i++) {
386 MapNode &n = data[i];
387 if (n.getContent() != CONTENT_AIR) {
396 // Set member variable
397 m_day_night_differs = differs;
400 void MapBlock::expireDayNightDiff()
403 m_day_night_differs = false;
404 m_day_night_differs_expired = false;
408 m_day_night_differs_expired = true;
411 s16 MapBlock::getGroundLevel(v2s16 p2d)
417 s16 y = MAP_BLOCKSIZE-1;
420 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
421 if (m_gamedef->ndef()->get(n).walkable) {
422 if(y == MAP_BLOCKSIZE-1)
430 catch(InvalidPositionException &e)
439 // List relevant id-name pairs for ids in the block using nodedef
440 // Renumbers the content IDs (starting at 0 and incrementing
441 // use static memory requires about 65535 * sizeof(int) ram in order to be
442 // sure we can handle all content ids. But it's absolutely worth it as it's
443 // a speedup of 4 for one of the major time consuming functions on storing
445 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
446 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
447 INodeDefManager *nodedef)
449 memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
451 std::set<content_t> unknown_contents;
452 content_t id_counter = 0;
453 for (u32 i = 0; i < MapBlock::nodecount; i++) {
454 content_t global_id = nodes[i].getContent();
455 content_t id = CONTENT_IGNORE;
457 // Try to find an existing mapping
458 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
459 id = getBlockNodeIdMapping_mapping[global_id];
463 // We have to assign a new mapping
465 getBlockNodeIdMapping_mapping[global_id] = id;
467 const ContentFeatures &f = nodedef->get(global_id);
468 const std::string &name = f.name;
470 unknown_contents.insert(global_id);
472 nimap->set(id, name);
475 // Update the MapNode
476 nodes[i].setContent(id);
478 for (u16 unknown_content : unknown_contents) {
479 errorstream << "getBlockNodeIdMapping(): IGNORING ERROR: "
480 << "Name for node id " << unknown_content << " not known" << std::endl;
483 // Correct ids in the block to match nodedef based on names.
484 // Unknown ones are added to nodedef.
485 // Will not update itself to match id-name pairs in nodedef.
486 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
489 INodeDefManager *nodedef = gamedef->ndef();
490 // This means the block contains incorrect ids, and we contain
491 // the information to convert those to names.
492 // nodedef contains information to convert our names to globally
494 std::unordered_set<content_t> unnamed_contents;
495 std::unordered_set<std::string> unallocatable_contents;
497 bool previous_exists = false;
498 content_t previous_local_id = CONTENT_IGNORE;
499 content_t previous_global_id = CONTENT_IGNORE;
501 for (u32 i = 0; i < MapBlock::nodecount; i++) {
502 content_t local_id = nodes[i].getContent();
503 // If previous node local_id was found and same than before, don't lookup maps
504 // apply directly previous resolved id
505 // This permits to massively improve loading performance when nodes are similar
506 // example: default:air, default:stone are massively present
507 if (previous_exists && local_id == previous_local_id) {
508 nodes[i].setContent(previous_global_id);
513 if (!nimap->getName(local_id, name)) {
514 unnamed_contents.insert(local_id);
515 previous_exists = false;
520 if (!nodedef->getId(name, global_id)) {
521 global_id = gamedef->allocateUnknownNodeId(name);
522 if (global_id == CONTENT_IGNORE) {
523 unallocatable_contents.insert(name);
524 previous_exists = false;
528 nodes[i].setContent(global_id);
530 // Save previous node local_id & global_id result
531 previous_local_id = local_id;
532 previous_global_id = global_id;
533 previous_exists = true;
536 for (const content_t c: unnamed_contents) {
537 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
538 << "Block contains id " << c
539 << " with no name mapping" << std::endl;
541 for (const std::string &node_name: unallocatable_contents) {
542 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
543 << "Could not allocate global id for node name \""
544 << node_name << "\"" << std::endl;
548 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
550 if(!ser_ver_supported(version))
551 throw VersionMismatchException("ERROR: MapBlock format not supported");
554 throw SerializationError("ERROR: Not writing dummy block.");
556 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
562 if(getDayNightDiff())
568 writeU16(os, m_lighting_complete);
577 MapNode *tmp_nodes = new MapNode[nodecount];
578 for(u32 i=0; i<nodecount; i++)
579 tmp_nodes[i] = data[i];
580 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
582 u8 content_width = 2;
584 writeU8(os, content_width);
585 writeU8(os, params_width);
586 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
587 content_width, params_width, true);
592 u8 content_width = 2;
594 writeU8(os, content_width);
595 writeU8(os, params_width);
596 MapNode::serializeBulk(os, version, data, nodecount,
597 content_width, params_width, true);
603 std::ostringstream oss(std::ios_base::binary);
604 m_node_metadata.serialize(oss, version, disk);
605 compressZlib(oss.str(), os);
608 Data that goes to disk, but not the network
614 m_node_timers.serialize(os, version);
618 m_static_objects.serialize(os);
621 writeU32(os, getTimestamp());
623 // Write block-specific node definition id mapping
628 m_node_timers.serialize(os, version);
633 void MapBlock::serializeNetworkSpecific(std::ostream &os)
636 throw SerializationError("ERROR: Not writing dummy block.");
639 writeU8(os, 1); // version
640 writeF1000(os, 0); // deprecated heat
641 writeF1000(os, 0); // deprecated humidity
644 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
646 if(!ser_ver_supported(version))
647 throw VersionMismatchException("ERROR: MapBlock format not supported");
649 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
651 m_day_night_differs_expired = false;
655 deSerialize_pre22(is, version, disk);
659 u8 flags = readU8(is);
660 is_underground = (flags & 0x01) != 0;
661 m_day_night_differs = (flags & 0x02) != 0;
663 m_lighting_complete = 0xFFFF;
665 m_lighting_complete = readU16(is);
666 m_generated = (flags & 0x08) == 0;
671 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
672 <<": Bulk node data"<<std::endl);
673 u8 content_width = readU8(is);
674 u8 params_width = readU8(is);
675 if(content_width != 1 && content_width != 2)
676 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
677 if(params_width != 2)
678 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
679 MapNode::deSerializeBulk(is, version, data, nodecount,
680 content_width, params_width, true);
685 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
686 <<": Node metadata"<<std::endl);
689 std::ostringstream oss(std::ios_base::binary);
690 decompressZlib(is, oss);
691 std::istringstream iss(oss.str(), std::ios_base::binary);
693 m_node_metadata.deSerialize(iss, m_gamedef->idef());
695 content_nodemeta_deserialize_legacy(iss,
696 &m_node_metadata, &m_node_timers,
698 } catch(SerializationError &e) {
699 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
700 <<" while deserializing node metadata at ("
701 <<PP(getPos())<<": "<<e.what()<<std::endl;
705 Data that is only on disk
715 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
716 <<": Node timers (ver==24)"<<std::endl);
717 m_node_timers.deSerialize(is, version);
721 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
722 <<": Static objects"<<std::endl);
723 m_static_objects.deSerialize(is);
726 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
727 <<": Timestamp"<<std::endl);
728 setTimestamp(readU32(is));
729 m_disk_timestamp = m_timestamp;
731 // Dynamically re-set ids based on node names
732 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
733 <<": NameIdMapping"<<std::endl);
735 nimap.deSerialize(is);
736 correctBlockNodeIds(&nimap, data, m_gamedef);
739 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
740 <<": Node timers (ver>=25)"<<std::endl);
741 m_node_timers.deSerialize(is, version);
745 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
746 <<": Done."<<std::endl);
749 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
752 int version = readU8(is);
754 // throw SerializationError("unsupported MapBlock version");
756 readF1000(is); // deprecated heat
757 readF1000(is); // deprecated humidity
760 catch(SerializationError &e)
762 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
763 <<": "<<e.what()<<std::endl;
771 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
773 // Initialize default flags
774 is_underground = false;
775 m_day_night_differs = false;
776 m_lighting_complete = 0xFFFF;
779 // Make a temporary buffer
780 u32 ser_length = MapNode::serializedLength(version);
781 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
783 // These have no compression
784 if (version <= 3 || version == 5 || version == 6) {
787 if (is.gcount() != 1)
788 throw SerializationError(std::string(FUNCTION_NAME)
789 + ": not enough input data");
790 is_underground = tmp;
791 is.read((char *)*databuf_nodelist, nodecount * ser_length);
792 if ((u32)is.gcount() != nodecount * ser_length)
793 throw SerializationError(std::string(FUNCTION_NAME)
794 + ": not enough input data");
795 } else if (version <= 10) {
797 is.read((char *)&t8, 1);
801 // Uncompress and set material data
802 std::ostringstream os(std::ios_base::binary);
803 decompress(is, os, version);
804 std::string s = os.str();
805 if (s.size() != nodecount)
806 throw SerializationError(std::string(FUNCTION_NAME)
807 + ": not enough input data");
808 for (u32 i = 0; i < s.size(); i++) {
809 databuf_nodelist[i*ser_length] = s[i];
813 // Uncompress and set param data
814 std::ostringstream os(std::ios_base::binary);
815 decompress(is, os, version);
816 std::string s = os.str();
817 if (s.size() != nodecount)
818 throw SerializationError(std::string(FUNCTION_NAME)
819 + ": not enough input data");
820 for (u32 i = 0; i < s.size(); i++) {
821 databuf_nodelist[i*ser_length + 1] = s[i];
826 // Uncompress and set param2 data
827 std::ostringstream os(std::ios_base::binary);
828 decompress(is, os, version);
829 std::string s = os.str();
830 if (s.size() != nodecount)
831 throw SerializationError(std::string(FUNCTION_NAME)
832 + ": not enough input data");
833 for (u32 i = 0; i < s.size(); i++) {
834 databuf_nodelist[i*ser_length + 2] = s[i];
837 } else { // All other versions (10 to 21)
839 is.read((char*)&flags, 1);
840 is_underground = (flags & 0x01) != 0;
841 m_day_night_differs = (flags & 0x02) != 0;
843 m_generated = (flags & 0x08) == 0;
846 std::ostringstream os(std::ios_base::binary);
847 decompress(is, os, version);
848 std::string s = os.str();
849 if (s.size() != nodecount * 3)
850 throw SerializationError(std::string(FUNCTION_NAME)
851 + ": decompress resulted in size other than nodecount*3");
853 // deserialize nodes from buffer
854 for (u32 i = 0; i < nodecount; i++) {
855 databuf_nodelist[i*ser_length] = s[i];
856 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
857 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
867 std::string data = deSerializeString(is);
868 std::istringstream iss(data, std::ios_base::binary);
869 content_nodemeta_deserialize_legacy(iss,
870 &m_node_metadata, &m_node_timers,
873 //std::string data = deSerializeLongString(is);
874 std::ostringstream oss(std::ios_base::binary);
875 decompressZlib(is, oss);
876 std::istringstream iss(oss.str(), std::ios_base::binary);
877 content_nodemeta_deserialize_legacy(iss,
878 &m_node_metadata, &m_node_timers,
881 } catch(SerializationError &e) {
882 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
883 <<" while deserializing node metadata"<<std::endl;
888 // Deserialize node data
889 for (u32 i = 0; i < nodecount; i++) {
890 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 warningstream<<"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, sizeof(spos), "(%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 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
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->getNodeNoEx(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);