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
369 for (u32 i = 0; i < nodecount; i++) {
370 MapNode &n = data[i];
372 differs = !n.isLightDayNightEq(nodemgr);
378 If some lighting values differ, check if the whole thing is
379 just air. If it is just air, differs = false
382 bool only_air = true;
383 for (u32 i = 0; i < nodecount; i++) {
384 MapNode &n = data[i];
385 if (n.getContent() != CONTENT_AIR) {
394 // Set member variable
395 m_day_night_differs = differs;
398 void MapBlock::expireDayNightDiff()
401 m_day_night_differs = false;
402 m_day_night_differs_expired = false;
406 m_day_night_differs_expired = true;
409 s16 MapBlock::getGroundLevel(v2s16 p2d)
415 s16 y = MAP_BLOCKSIZE-1;
418 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
419 if(m_gamedef->ndef()->get(n).walkable)
421 if(y == MAP_BLOCKSIZE-1)
429 catch(InvalidPositionException &e)
438 // List relevant id-name pairs for ids in the block using nodedef
439 // Renumbers the content IDs (starting at 0 and incrementing
440 // use static memory requires about 65535 * sizeof(int) ram in order to be
441 // sure we can handle all content ids. But it's absolutely worth it as it's
442 // a speedup of 4 for one of the major time consuming functions on storing
444 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
445 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
446 INodeDefManager *nodedef)
448 memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
450 std::set<content_t> unknown_contents;
451 content_t id_counter = 0;
452 for (u32 i = 0; i < MapBlock::nodecount; i++) {
453 content_t global_id = nodes[i].getContent();
454 content_t id = CONTENT_IGNORE;
456 // Try to find an existing mapping
457 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
458 id = getBlockNodeIdMapping_mapping[global_id];
462 // We have to assign a new mapping
464 getBlockNodeIdMapping_mapping[global_id] = id;
466 const ContentFeatures &f = nodedef->get(global_id);
467 const std::string &name = f.name;
469 unknown_contents.insert(global_id);
471 nimap->set(id, name);
474 // Update the MapNode
475 nodes[i].setContent(id);
477 for(std::set<content_t>::const_iterator
478 i = unknown_contents.begin();
479 i != unknown_contents.end(); ++i){
480 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
481 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
484 // Correct ids in the block to match nodedef based on names.
485 // Unknown ones are added to nodedef.
486 // Will not update itself to match id-name pairs in nodedef.
487 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
490 INodeDefManager *nodedef = gamedef->ndef();
491 // This means the block contains incorrect ids, and we contain
492 // the information to convert those to names.
493 // nodedef contains information to convert our names to globally
495 std::unordered_set<content_t> unnamed_contents;
496 std::unordered_set<std::string> unallocatable_contents;
498 bool previous_was_found = false;
499 content_t previous_local_id = CONTENT_IGNORE;
500 content_t previous_global_id = CONTENT_IGNORE;
502 for (u32 i = 0; i < MapBlock::nodecount; i++) {
503 content_t local_id = nodes[i].getContent();
504 // If previous node local_id was found and same than before, don't lookup maps
505 // apply directly previous resolved id
506 // This permits to massively improve loading performance when nodes are similar
507 // example: default:air, default:stone are massively present
508 if (previous_was_found && local_id == previous_local_id) {
509 nodes[i].setContent(previous_global_id);
514 if (!nimap->getName(local_id, name)) {
515 unnamed_contents.insert(local_id);
516 previous_was_found = false;
521 if (!nodedef->getId(name, global_id)) {
522 global_id = gamedef->allocateUnknownNodeId(name);
523 if (global_id == CONTENT_IGNORE) {
524 unallocatable_contents.insert(name);
525 previous_was_found = false;
529 nodes[i].setContent(global_id);
531 // Save previous node local_id & global_id result
532 previous_local_id = local_id;
533 previous_global_id = global_id;
534 previous_was_found = true;
537 for (const content_t c: unnamed_contents) {
538 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
539 << "Block contains id " << c
540 << " with no name mapping" << std::endl;
542 for (const std::string &node_name: unallocatable_contents) {
543 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
544 << "Could not allocate global id for node name \""
545 << node_name << "\"" << std::endl;
549 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
551 if(!ser_ver_supported(version))
552 throw VersionMismatchException("ERROR: MapBlock format not supported");
555 throw SerializationError("ERROR: Not writing dummy block.");
557 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
563 if(getDayNightDiff())
565 if(m_generated == false)
569 writeU16(os, m_lighting_complete);
578 MapNode *tmp_nodes = new MapNode[nodecount];
579 for(u32 i=0; i<nodecount; i++)
580 tmp_nodes[i] = data[i];
581 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
583 u8 content_width = 2;
585 writeU8(os, content_width);
586 writeU8(os, params_width);
587 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
588 content_width, params_width, true);
593 u8 content_width = 2;
595 writeU8(os, content_width);
596 writeU8(os, params_width);
597 MapNode::serializeBulk(os, version, data, nodecount,
598 content_width, params_width, true);
604 std::ostringstream oss(std::ios_base::binary);
605 m_node_metadata.serialize(oss, version, disk);
606 compressZlib(oss.str(), os);
609 Data that goes to disk, but not the network
615 m_node_timers.serialize(os, version);
619 m_static_objects.serialize(os);
622 writeU32(os, getTimestamp());
624 // Write block-specific node definition id mapping
629 m_node_timers.serialize(os, version);
634 void MapBlock::serializeNetworkSpecific(std::ostream &os)
637 throw SerializationError("ERROR: Not writing dummy block.");
640 writeU8(os, 1); // version
641 writeF1000(os, 0); // deprecated heat
642 writeF1000(os, 0); // deprecated humidity
645 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
647 if(!ser_ver_supported(version))
648 throw VersionMismatchException("ERROR: MapBlock format not supported");
650 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
652 m_day_night_differs_expired = false;
656 deSerialize_pre22(is, version, disk);
660 u8 flags = readU8(is);
661 is_underground = (flags & 0x01) ? true : false;
662 m_day_night_differs = (flags & 0x02) ? true : false;
664 m_lighting_complete = 0xFFFF;
666 m_lighting_complete = readU16(is);
667 m_generated = (flags & 0x08) ? false : true;
672 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
673 <<": Bulk node data"<<std::endl);
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->idef());
696 content_nodemeta_deserialize_legacy(iss,
697 &m_node_metadata, &m_node_timers,
699 } catch(SerializationError &e) {
700 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
701 <<" while deserializing node metadata at ("
702 <<PP(getPos())<<": "<<e.what()<<std::endl;
706 Data that is only on disk
716 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
717 <<": Node timers (ver==24)"<<std::endl);
718 m_node_timers.deSerialize(is, version);
722 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
723 <<": Static objects"<<std::endl);
724 m_static_objects.deSerialize(is);
727 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
728 <<": Timestamp"<<std::endl);
729 setTimestamp(readU32(is));
730 m_disk_timestamp = m_timestamp;
732 // Dynamically re-set ids based on node names
733 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
734 <<": NameIdMapping"<<std::endl);
736 nimap.deSerialize(is);
737 correctBlockNodeIds(&nimap, data, m_gamedef);
740 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
741 <<": Node timers (ver>=25)"<<std::endl);
742 m_node_timers.deSerialize(is, version);
746 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
747 <<": Done."<<std::endl);
750 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
753 int version = readU8(is);
755 // throw SerializationError("unsupported MapBlock version");
757 readF1000(is); // deprecated heat
758 readF1000(is); // deprecated humidity
761 catch(SerializationError &e)
763 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
764 <<": "<<e.what()<<std::endl;
772 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
774 // Initialize default flags
775 is_underground = false;
776 m_day_night_differs = false;
777 m_lighting_complete = 0xFFFF;
780 // Make a temporary buffer
781 u32 ser_length = MapNode::serializedLength(version);
782 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
784 // These have no compression
785 if (version <= 3 || version == 5 || version == 6) {
788 if (is.gcount() != 1)
789 throw SerializationError(std::string(FUNCTION_NAME)
790 + ": not enough input data");
791 is_underground = tmp;
792 is.read((char *)*databuf_nodelist, nodecount * ser_length);
793 if ((u32)is.gcount() != nodecount * ser_length)
794 throw SerializationError(std::string(FUNCTION_NAME)
795 + ": not enough input data");
796 } else if (version <= 10) {
798 is.read((char *)&t8, 1);
802 // Uncompress and set material data
803 std::ostringstream os(std::ios_base::binary);
804 decompress(is, os, version);
805 std::string s = os.str();
806 if (s.size() != nodecount)
807 throw SerializationError(std::string(FUNCTION_NAME)
808 + ": not enough input data");
809 for (u32 i = 0; i < s.size(); i++) {
810 databuf_nodelist[i*ser_length] = s[i];
814 // Uncompress and set param data
815 std::ostringstream os(std::ios_base::binary);
816 decompress(is, os, version);
817 std::string s = os.str();
818 if (s.size() != nodecount)
819 throw SerializationError(std::string(FUNCTION_NAME)
820 + ": not enough input data");
821 for (u32 i = 0; i < s.size(); i++) {
822 databuf_nodelist[i*ser_length + 1] = s[i];
827 // Uncompress and set param2 data
828 std::ostringstream os(std::ios_base::binary);
829 decompress(is, os, version);
830 std::string s = os.str();
831 if (s.size() != nodecount)
832 throw SerializationError(std::string(FUNCTION_NAME)
833 + ": not enough input data");
834 for (u32 i = 0; i < s.size(); i++) {
835 databuf_nodelist[i*ser_length + 2] = s[i];
838 } else { // All other versions (10 to 21)
840 is.read((char*)&flags, 1);
841 is_underground = (flags & 0x01) ? true : false;
842 m_day_night_differs = (flags & 0x02) ? true : false;
844 m_generated = (flags & 0x08) ? false : true;
847 std::ostringstream os(std::ios_base::binary);
848 decompress(is, os, version);
849 std::string s = os.str();
850 if (s.size() != nodecount * 3)
851 throw SerializationError(std::string(FUNCTION_NAME)
852 + ": decompress resulted in size other than nodecount*3");
854 // deserialize nodes from buffer
855 for (u32 i = 0; i < nodecount; i++) {
856 databuf_nodelist[i*ser_length] = s[i];
857 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
858 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
868 std::string data = deSerializeString(is);
869 std::istringstream iss(data, std::ios_base::binary);
870 content_nodemeta_deserialize_legacy(iss,
871 &m_node_metadata, &m_node_timers,
874 //std::string data = deSerializeLongString(is);
875 std::ostringstream oss(std::ios_base::binary);
876 decompressZlib(is, oss);
877 std::istringstream iss(oss.str(), std::ios_base::binary);
878 content_nodemeta_deserialize_legacy(iss,
879 &m_node_metadata, &m_node_timers,
882 } catch(SerializationError &e) {
883 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
884 <<" while deserializing node metadata"<<std::endl;
889 // Deserialize node data
890 for (u32 i = 0; i < nodecount; i++) {
891 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
896 Versions up from 9 have block objects. (DEPRECATED)
899 u16 count = readU16(is);
900 // Not supported and length not known if count is not 0
902 warningstream<<"MapBlock::deSerialize_pre22(): "
903 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
909 Versions up from 15 have static objects.
912 m_static_objects.deSerialize(is);
916 setTimestamp(readU32(is));
917 m_disk_timestamp = m_timestamp;
919 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
922 // Dynamically re-set ids based on node names
924 // If supported, read node definition id mapping
926 nimap.deSerialize(is);
927 // Else set the legacy mapping
929 content_mapnode_get_name_id_mapping(&nimap);
931 correctBlockNodeIds(&nimap, data, m_gamedef);
935 // Legacy data changes
936 // This code has to convert from pre-22 to post-22 format.
937 INodeDefManager *nodedef = m_gamedef->ndef();
938 for(u32 i=0; i<nodecount; i++)
940 const ContentFeatures &f = nodedef->get(data[i].getContent());
942 if(nodedef->getId("default:stone") == data[i].getContent()
943 && data[i].getParam1() == 1)
945 data[i].setContent(nodedef->getId("default:stone_with_coal"));
946 data[i].setParam1(0);
948 else if(nodedef->getId("default:stone") == data[i].getContent()
949 && data[i].getParam1() == 2)
951 data[i].setContent(nodedef->getId("default:stone_with_iron"));
952 data[i].setParam1(0);
955 if(f.legacy_facedir_simple)
957 data[i].setParam2(data[i].getParam1());
958 data[i].setParam1(0);
961 if(f.legacy_wallmounted)
963 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
964 u8 dir_old_format = data[i].getParam2();
965 u8 dir_new_format = 0;
966 for(u8 j=0; j<8; j++)
968 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
974 data[i].setParam2(dir_new_format);
981 Get a quick string to describe what a block actually contains
983 std::string analyze_block(MapBlock *block)
988 std::ostringstream desc;
990 v3s16 p = block->getPos();
992 snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
995 switch(block->getModified())
997 case MOD_STATE_CLEAN:
1000 case MOD_STATE_WRITE_AT_UNLOAD:
1001 desc<<"WRITE_AT_UNLOAD, ";
1003 case MOD_STATE_WRITE_NEEDED:
1004 desc<<"WRITE_NEEDED, ";
1007 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1010 if(block->isGenerated())
1011 desc<<"is_gen [X], ";
1013 desc<<"is_gen [ ], ";
1015 if(block->getIsUnderground())
1016 desc<<"is_ug [X], ";
1018 desc<<"is_ug [ ], ";
1020 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1022 if(block->isDummy())
1028 bool full_ignore = true;
1029 bool some_ignore = false;
1030 bool full_air = true;
1031 bool some_air = false;
1032 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1033 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1034 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1037 MapNode n = block->getNodeNoEx(p);
1038 content_t c = n.getContent();
1039 if(c == CONTENT_IGNORE)
1042 full_ignore = false;
1043 if(c == CONTENT_AIR)
1051 std::ostringstream ss;
1054 ss<<"IGNORE (full), ";
1055 else if(some_ignore)
1063 if(ss.str().size()>=2)
1064 desc<<ss.str().substr(0, ss.str().size()-2);
1069 return desc.str().substr(0, desc.str().size()-2);