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
254 else if(nodemgr->get(n).light_propagates == false)
256 // A solid object is on the way.
257 stopped_to_solid_object = true;
265 current_light = diminish_light(current_light);
268 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
270 if(current_light > old_light || remove_light)
272 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
275 if(diminish_light(current_light) != 0)
277 light_sources.insert(pos_relative + pos);
280 if(current_light == 0 && stopped_to_solid_object)
284 *black_air_left = true;
289 // Whether or not the block below should see LIGHT_SUN
290 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
293 If the block below hasn't already been marked invalid:
295 Check if the node below the block has proper sunlight at top.
296 If not, the block below is invalid.
298 Ignore non-transparent nodes as they always have no light
301 if(block_below_is_valid)
303 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
304 if (is_valid_position) {
305 if(nodemgr->get(n).light_propagates)
307 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
308 && sunlight_should_go_down == false)
309 block_below_is_valid = false;
310 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
311 && sunlight_should_go_down == true)
312 block_below_is_valid = false;
317 /*std::cout<<"InvalidBlockException for bottom block node"
319 // Just no block below, no need to panic.
325 return block_below_is_valid;
329 void MapBlock::copyTo(VoxelManipulator &dst)
331 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
332 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
334 // Copy from data to VoxelManipulator
335 dst.copyFrom(data, data_area, v3s16(0,0,0),
336 getPosRelative(), data_size);
339 void MapBlock::copyFrom(VoxelManipulator &dst)
341 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
342 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
344 // Copy from VoxelManipulator to data
345 dst.copyTo(data, data_area, v3s16(0,0,0),
346 getPosRelative(), data_size);
349 void MapBlock::actuallyUpdateDayNightDiff()
351 INodeDefManager *nodemgr = m_gamedef->ndef();
353 // Running this function un-expires m_day_night_differs
354 m_day_night_differs_expired = false;
357 m_day_night_differs = false;
361 bool differs = false;
364 Check if any lighting value differs
367 MapNode previous_n(CONTENT_IGNORE);
368 for (u32 i = 0; i < nodecount; i++) {
371 // If node is identical to previous node, don't verify if it differs
375 differs = !n.isLightDayNightEq(nodemgr);
382 If some lighting values differ, check if the whole thing is
383 just air. If it is just air, differs = false
386 bool only_air = true;
387 for (u32 i = 0; i < nodecount; i++) {
388 MapNode &n = data[i];
389 if (n.getContent() != CONTENT_AIR) {
398 // Set member variable
399 m_day_night_differs = differs;
402 void MapBlock::expireDayNightDiff()
405 m_day_night_differs = false;
406 m_day_night_differs_expired = false;
410 m_day_night_differs_expired = true;
413 s16 MapBlock::getGroundLevel(v2s16 p2d)
419 s16 y = MAP_BLOCKSIZE-1;
422 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
423 if (m_gamedef->ndef()->get(n).walkable) {
424 if(y == MAP_BLOCKSIZE-1)
432 catch(InvalidPositionException &e)
441 // List relevant id-name pairs for ids in the block using nodedef
442 // Renumbers the content IDs (starting at 0 and incrementing
443 // use static memory requires about 65535 * sizeof(int) ram in order to be
444 // sure we can handle all content ids. But it's absolutely worth it as it's
445 // a speedup of 4 for one of the major time consuming functions on storing
447 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
448 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
449 INodeDefManager *nodedef)
451 memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
453 std::set<content_t> unknown_contents;
454 content_t id_counter = 0;
455 for (u32 i = 0; i < MapBlock::nodecount; i++) {
456 content_t global_id = nodes[i].getContent();
457 content_t id = CONTENT_IGNORE;
459 // Try to find an existing mapping
460 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
461 id = getBlockNodeIdMapping_mapping[global_id];
465 // We have to assign a new mapping
467 getBlockNodeIdMapping_mapping[global_id] = id;
469 const ContentFeatures &f = nodedef->get(global_id);
470 const std::string &name = f.name;
472 unknown_contents.insert(global_id);
474 nimap->set(id, name);
477 // Update the MapNode
478 nodes[i].setContent(id);
480 for (u16 unknown_content : unknown_contents) {
481 errorstream << "getBlockNodeIdMapping(): IGNORING ERROR: "
482 << "Name for node id " << unknown_content << " not known" << std::endl;
485 // Correct ids in the block to match nodedef based on names.
486 // Unknown ones are added to nodedef.
487 // Will not update itself to match id-name pairs in nodedef.
488 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
491 INodeDefManager *nodedef = gamedef->ndef();
492 // This means the block contains incorrect ids, and we contain
493 // the information to convert those to names.
494 // nodedef contains information to convert our names to globally
496 std::unordered_set<content_t> unnamed_contents;
497 std::unordered_set<std::string> unallocatable_contents;
499 bool previous_exists = false;
500 content_t previous_local_id = CONTENT_IGNORE;
501 content_t previous_global_id = CONTENT_IGNORE;
503 for (u32 i = 0; i < MapBlock::nodecount; i++) {
504 content_t local_id = nodes[i].getContent();
505 // If previous node local_id was found and same than before, don't lookup maps
506 // apply directly previous resolved id
507 // This permits to massively improve loading performance when nodes are similar
508 // example: default:air, default:stone are massively present
509 if (previous_exists && local_id == previous_local_id) {
510 nodes[i].setContent(previous_global_id);
515 if (!nimap->getName(local_id, name)) {
516 unnamed_contents.insert(local_id);
517 previous_exists = false;
522 if (!nodedef->getId(name, global_id)) {
523 global_id = gamedef->allocateUnknownNodeId(name);
524 if (global_id == CONTENT_IGNORE) {
525 unallocatable_contents.insert(name);
526 previous_exists = false;
530 nodes[i].setContent(global_id);
532 // Save previous node local_id & global_id result
533 previous_local_id = local_id;
534 previous_global_id = global_id;
535 previous_exists = true;
538 for (const content_t c: unnamed_contents) {
539 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
540 << "Block contains id " << c
541 << " with no name mapping" << std::endl;
543 for (const std::string &node_name: unallocatable_contents) {
544 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
545 << "Could not allocate global id for node name \""
546 << node_name << "\"" << std::endl;
550 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
552 if(!ser_ver_supported(version))
553 throw VersionMismatchException("ERROR: MapBlock format not supported");
556 throw SerializationError("ERROR: Not writing dummy block.");
558 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
564 if(getDayNightDiff())
566 if(m_generated == false)
570 writeU16(os, m_lighting_complete);
579 MapNode *tmp_nodes = new MapNode[nodecount];
580 for(u32 i=0; i<nodecount; i++)
581 tmp_nodes[i] = data[i];
582 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
584 u8 content_width = 2;
586 writeU8(os, content_width);
587 writeU8(os, params_width);
588 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
589 content_width, params_width, true);
594 u8 content_width = 2;
596 writeU8(os, content_width);
597 writeU8(os, params_width);
598 MapNode::serializeBulk(os, version, data, nodecount,
599 content_width, params_width, true);
605 std::ostringstream oss(std::ios_base::binary);
606 m_node_metadata.serialize(oss, version, disk);
607 compressZlib(oss.str(), os);
610 Data that goes to disk, but not the network
616 m_node_timers.serialize(os, version);
620 m_static_objects.serialize(os);
623 writeU32(os, getTimestamp());
625 // Write block-specific node definition id mapping
630 m_node_timers.serialize(os, version);
635 void MapBlock::serializeNetworkSpecific(std::ostream &os)
638 throw SerializationError("ERROR: Not writing dummy block.");
641 writeU8(os, 1); // version
642 writeF1000(os, 0); // deprecated heat
643 writeF1000(os, 0); // deprecated humidity
646 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
648 if(!ser_ver_supported(version))
649 throw VersionMismatchException("ERROR: MapBlock format not supported");
651 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
653 m_day_night_differs_expired = false;
657 deSerialize_pre22(is, version, disk);
661 u8 flags = readU8(is);
662 is_underground = (flags & 0x01) ? true : false;
663 m_day_night_differs = (flags & 0x02) ? true : false;
665 m_lighting_complete = 0xFFFF;
667 m_lighting_complete = readU16(is);
668 m_generated = (flags & 0x08) ? false : true;
673 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
674 <<": Bulk node data"<<std::endl);
675 u8 content_width = readU8(is);
676 u8 params_width = readU8(is);
677 if(content_width != 1 && content_width != 2)
678 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
679 if(params_width != 2)
680 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
681 MapNode::deSerializeBulk(is, version, data, nodecount,
682 content_width, params_width, true);
687 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
688 <<": Node metadata"<<std::endl);
691 std::ostringstream oss(std::ios_base::binary);
692 decompressZlib(is, oss);
693 std::istringstream iss(oss.str(), std::ios_base::binary);
695 m_node_metadata.deSerialize(iss, m_gamedef->idef());
697 content_nodemeta_deserialize_legacy(iss,
698 &m_node_metadata, &m_node_timers,
700 } catch(SerializationError &e) {
701 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
702 <<" while deserializing node metadata at ("
703 <<PP(getPos())<<": "<<e.what()<<std::endl;
707 Data that is only on disk
717 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
718 <<": Node timers (ver==24)"<<std::endl);
719 m_node_timers.deSerialize(is, version);
723 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
724 <<": Static objects"<<std::endl);
725 m_static_objects.deSerialize(is);
728 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
729 <<": Timestamp"<<std::endl);
730 setTimestamp(readU32(is));
731 m_disk_timestamp = m_timestamp;
733 // Dynamically re-set ids based on node names
734 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
735 <<": NameIdMapping"<<std::endl);
737 nimap.deSerialize(is);
738 correctBlockNodeIds(&nimap, data, m_gamedef);
741 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
742 <<": Node timers (ver>=25)"<<std::endl);
743 m_node_timers.deSerialize(is, version);
747 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
748 <<": Done."<<std::endl);
751 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
754 int version = readU8(is);
756 // throw SerializationError("unsupported MapBlock version");
758 readF1000(is); // deprecated heat
759 readF1000(is); // deprecated humidity
762 catch(SerializationError &e)
764 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
765 <<": "<<e.what()<<std::endl;
773 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
775 // Initialize default flags
776 is_underground = false;
777 m_day_night_differs = false;
778 m_lighting_complete = 0xFFFF;
781 // Make a temporary buffer
782 u32 ser_length = MapNode::serializedLength(version);
783 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
785 // These have no compression
786 if (version <= 3 || version == 5 || version == 6) {
789 if (is.gcount() != 1)
790 throw SerializationError(std::string(FUNCTION_NAME)
791 + ": not enough input data");
792 is_underground = tmp;
793 is.read((char *)*databuf_nodelist, nodecount * ser_length);
794 if ((u32)is.gcount() != nodecount * ser_length)
795 throw SerializationError(std::string(FUNCTION_NAME)
796 + ": not enough input data");
797 } else if (version <= 10) {
799 is.read((char *)&t8, 1);
803 // Uncompress and set material data
804 std::ostringstream os(std::ios_base::binary);
805 decompress(is, os, version);
806 std::string s = os.str();
807 if (s.size() != nodecount)
808 throw SerializationError(std::string(FUNCTION_NAME)
809 + ": not enough input data");
810 for (u32 i = 0; i < s.size(); i++) {
811 databuf_nodelist[i*ser_length] = s[i];
815 // Uncompress and set param data
816 std::ostringstream os(std::ios_base::binary);
817 decompress(is, os, version);
818 std::string s = os.str();
819 if (s.size() != nodecount)
820 throw SerializationError(std::string(FUNCTION_NAME)
821 + ": not enough input data");
822 for (u32 i = 0; i < s.size(); i++) {
823 databuf_nodelist[i*ser_length + 1] = s[i];
828 // Uncompress and set param2 data
829 std::ostringstream os(std::ios_base::binary);
830 decompress(is, os, version);
831 std::string s = os.str();
832 if (s.size() != nodecount)
833 throw SerializationError(std::string(FUNCTION_NAME)
834 + ": not enough input data");
835 for (u32 i = 0; i < s.size(); i++) {
836 databuf_nodelist[i*ser_length + 2] = s[i];
839 } else { // All other versions (10 to 21)
841 is.read((char*)&flags, 1);
842 is_underground = (flags & 0x01) ? true : false;
843 m_day_night_differs = (flags & 0x02) ? true : false;
845 m_generated = (flags & 0x08) ? false : true;
848 std::ostringstream os(std::ios_base::binary);
849 decompress(is, os, version);
850 std::string s = os.str();
851 if (s.size() != nodecount * 3)
852 throw SerializationError(std::string(FUNCTION_NAME)
853 + ": decompress resulted in size other than nodecount*3");
855 // deserialize nodes from buffer
856 for (u32 i = 0; i < nodecount; i++) {
857 databuf_nodelist[i*ser_length] = s[i];
858 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
859 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
869 std::string data = deSerializeString(is);
870 std::istringstream iss(data, std::ios_base::binary);
871 content_nodemeta_deserialize_legacy(iss,
872 &m_node_metadata, &m_node_timers,
875 //std::string data = deSerializeLongString(is);
876 std::ostringstream oss(std::ios_base::binary);
877 decompressZlib(is, oss);
878 std::istringstream iss(oss.str(), std::ios_base::binary);
879 content_nodemeta_deserialize_legacy(iss,
880 &m_node_metadata, &m_node_timers,
883 } catch(SerializationError &e) {
884 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
885 <<" while deserializing node metadata"<<std::endl;
890 // Deserialize node data
891 for (u32 i = 0; i < nodecount; i++) {
892 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
897 Versions up from 9 have block objects. (DEPRECATED)
900 u16 count = readU16(is);
901 // Not supported and length not known if count is not 0
903 warningstream<<"MapBlock::deSerialize_pre22(): "
904 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
910 Versions up from 15 have static objects.
913 m_static_objects.deSerialize(is);
917 setTimestamp(readU32(is));
918 m_disk_timestamp = m_timestamp;
920 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
923 // Dynamically re-set ids based on node names
925 // If supported, read node definition id mapping
927 nimap.deSerialize(is);
928 // Else set the legacy mapping
930 content_mapnode_get_name_id_mapping(&nimap);
932 correctBlockNodeIds(&nimap, data, m_gamedef);
936 // Legacy data changes
937 // This code has to convert from pre-22 to post-22 format.
938 INodeDefManager *nodedef = m_gamedef->ndef();
939 for(u32 i=0; i<nodecount; i++)
941 const ContentFeatures &f = nodedef->get(data[i].getContent());
943 if(nodedef->getId("default:stone") == data[i].getContent()
944 && data[i].getParam1() == 1)
946 data[i].setContent(nodedef->getId("default:stone_with_coal"));
947 data[i].setParam1(0);
949 else if(nodedef->getId("default:stone") == data[i].getContent()
950 && data[i].getParam1() == 2)
952 data[i].setContent(nodedef->getId("default:stone_with_iron"));
953 data[i].setParam1(0);
956 if(f.legacy_facedir_simple)
958 data[i].setParam2(data[i].getParam1());
959 data[i].setParam1(0);
962 if(f.legacy_wallmounted)
964 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
965 u8 dir_old_format = data[i].getParam2();
966 u8 dir_new_format = 0;
967 for(u8 j=0; j<8; j++)
969 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
975 data[i].setParam2(dir_new_format);
982 Get a quick string to describe what a block actually contains
984 std::string analyze_block(MapBlock *block)
989 std::ostringstream desc;
991 v3s16 p = block->getPos();
993 snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
996 switch(block->getModified())
998 case MOD_STATE_CLEAN:
1001 case MOD_STATE_WRITE_AT_UNLOAD:
1002 desc<<"WRITE_AT_UNLOAD, ";
1004 case MOD_STATE_WRITE_NEEDED:
1005 desc<<"WRITE_NEEDED, ";
1008 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1011 if(block->isGenerated())
1012 desc<<"is_gen [X], ";
1014 desc<<"is_gen [ ], ";
1016 if(block->getIsUnderground())
1017 desc<<"is_ug [X], ";
1019 desc<<"is_ug [ ], ";
1021 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1023 if(block->isDummy())
1029 bool full_ignore = true;
1030 bool some_ignore = false;
1031 bool full_air = true;
1032 bool some_air = false;
1033 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1034 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1035 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1038 MapNode n = block->getNodeNoEx(p);
1039 content_t c = n.getContent();
1040 if(c == CONTENT_IGNORE)
1043 full_ignore = false;
1044 if(c == CONTENT_AIR)
1052 std::ostringstream ss;
1055 ss<<"IGNORE (full), ";
1056 else if(some_ignore)
1064 if(ss.str().size()>=2)
1065 desc<<ss.str().substr(0, ss.str().size()-2);
1070 return desc.str().substr(0, desc.str().size()-2);