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_nodemeta.h" // For legacy deserialization
31 #include "serialization.h"
33 #include "mapblock_mesh.h"
35 #include "util/string.h"
36 #include "util/serialize.h"
37 #include "util/basic_macros.h"
39 static const char *modified_reason_strings[] = {
48 "NodeMetaRef::reportMetadataChange",
50 "Timestamp expired (step)",
52 "removeRemovedObjects/remove",
53 "removeRemovedObjects/deactivate",
54 "Stored list cleared in activateObjects due to overflow",
55 "deactivateFarObjects: Static data moved in",
56 "deactivateFarObjects: Static data moved out",
57 "deactivateFarObjects: Static data changed considerably",
58 "finishBlockMake: expireDayNightDiff",
67 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
70 m_pos_relative(pos * MAP_BLOCKSIZE),
72 m_modified(MOD_STATE_WRITE_NEEDED),
73 m_modified_reason(MOD_REASON_INITIAL),
74 is_underground(false),
75 m_lighting_complete(0xFFFF),
76 m_day_night_differs(false),
77 m_day_night_differs_expired(true),
79 m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
80 m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
97 //MutexAutoLock lock(mesh_mutex);
111 bool MapBlock::isValidPositionParent(v3s16 p)
113 if(isValidPosition(p))
118 return m_parent->isValidPosition(getPosRelative() + p);
122 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
124 if (isValidPosition(p) == false)
125 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
128 if (is_valid_position)
129 *is_valid_position = false;
130 return MapNode(CONTENT_IGNORE);
132 if (is_valid_position)
133 *is_valid_position = true;
134 return data[p.Z * zstride + p.Y * ystride + p.X];
137 std::string MapBlock::getModifiedReasonString()
141 const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
142 ARRLEN(modified_reason_strings));
144 for (u32 i = 0; i != ubound; i++) {
145 if ((m_modified_reason & (1 << i)) == 0)
148 reason += modified_reason_strings[i];
152 if (reason.length() > 2)
153 reason.resize(reason.length() - 2);
159 Propagates sunlight down through the block.
160 Doesn't modify nodes that are not affected by sunlight.
162 Returns false if sunlight at bottom block is invalid.
163 Returns true if sunlight at bottom block is valid.
164 Returns true if bottom block doesn't exist.
166 If there is a block above, continues from it.
167 If there is no block above, assumes there is sunlight, unless
168 is_underground is set or highest node is water.
170 All sunlighted nodes are added to light_sources.
172 if remove_light==true, sets non-sunlighted nodes black.
174 if black_air_left!=NULL, it is set to true if non-sunlighted
175 air is left in block.
177 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
178 bool remove_light, bool *black_air_left)
180 INodeDefManager *nodemgr = m_gamedef->ndef();
182 // Whether the sunlight at the top of the bottom block is valid
183 bool block_below_is_valid = true;
185 v3s16 pos_relative = getPosRelative();
187 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
189 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
192 bool no_sunlight = false;
193 //bool no_top_block = false;
195 // Check if node above block has sunlight
197 bool is_valid_position;
198 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
200 if (is_valid_position)
202 if(n.getContent() == CONTENT_IGNORE)
205 no_sunlight = is_underground;
207 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
214 //no_top_block = true;
216 // NOTE: This makes over-ground roofed places sunlighted
217 // Assume sunlight, unless is_underground==true
224 MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
225 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
230 // NOTE: As of now, this just would make everything dark.
232 //no_sunlight = true;
235 #if 0 // Doesn't work; nothing gets light.
236 bool no_sunlight = true;
237 bool no_top_block = false;
238 // Check if node above block has sunlight
240 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
241 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
246 catch(InvalidPositionException &e)
252 /*std::cout<<"("<<x<<","<<z<<"): "
253 <<"no_top_block="<<no_top_block
254 <<", is_underground="<<is_underground
255 <<", no_sunlight="<<no_sunlight
258 s16 y = MAP_BLOCKSIZE-1;
260 // This makes difference to diminishing in water.
261 bool stopped_to_solid_object = false;
263 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
268 MapNode &n = getNodeRef(pos);
270 if(current_light == 0)
274 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
276 // Do nothing: Sunlight is continued
278 else if(nodemgr->get(n).light_propagates == false)
280 // A solid object is on the way.
281 stopped_to_solid_object = true;
289 current_light = diminish_light(current_light);
292 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
294 if(current_light > old_light || remove_light)
296 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
299 if(diminish_light(current_light) != 0)
301 light_sources.insert(pos_relative + pos);
304 if(current_light == 0 && stopped_to_solid_object)
308 *black_air_left = true;
313 // Whether or not the block below should see LIGHT_SUN
314 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
317 If the block below hasn't already been marked invalid:
319 Check if the node below the block has proper sunlight at top.
320 If not, the block below is invalid.
322 Ignore non-transparent nodes as they always have no light
325 if(block_below_is_valid)
327 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
328 if (is_valid_position) {
329 if(nodemgr->get(n).light_propagates)
331 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
332 && sunlight_should_go_down == false)
333 block_below_is_valid = false;
334 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
335 && sunlight_should_go_down == true)
336 block_below_is_valid = false;
341 /*std::cout<<"InvalidBlockException for bottom block node"
343 // Just no block below, no need to panic.
349 return block_below_is_valid;
353 void MapBlock::copyTo(VoxelManipulator &dst)
355 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
356 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
358 // Copy from data to VoxelManipulator
359 dst.copyFrom(data, data_area, v3s16(0,0,0),
360 getPosRelative(), data_size);
363 void MapBlock::copyFrom(VoxelManipulator &dst)
365 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
366 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
368 // Copy from VoxelManipulator to data
369 dst.copyTo(data, data_area, v3s16(0,0,0),
370 getPosRelative(), data_size);
373 void MapBlock::actuallyUpdateDayNightDiff()
375 INodeDefManager *nodemgr = m_gamedef->ndef();
377 // Running this function un-expires m_day_night_differs
378 m_day_night_differs_expired = false;
381 m_day_night_differs = false;
388 Check if any lighting value differs
390 for (u32 i = 0; i < nodecount; i++) {
391 MapNode &n = data[i];
393 differs = !n.isLightDayNightEq(nodemgr);
399 If some lighting values differ, check if the whole thing is
400 just air. If it is just air, differs = false
403 bool only_air = true;
404 for (u32 i = 0; i < nodecount; i++) {
405 MapNode &n = data[i];
406 if (n.getContent() != CONTENT_AIR) {
415 // Set member variable
416 m_day_night_differs = differs;
419 void MapBlock::expireDayNightDiff()
421 //INodeDefManager *nodemgr = m_gamedef->ndef();
424 m_day_night_differs = false;
425 m_day_night_differs_expired = false;
429 m_day_night_differs_expired = true;
432 s16 MapBlock::getGroundLevel(v2s16 p2d)
438 s16 y = MAP_BLOCKSIZE-1;
441 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
442 if(m_gamedef->ndef()->get(n).walkable)
444 if(y == MAP_BLOCKSIZE-1)
452 catch(InvalidPositionException &e)
461 // List relevant id-name pairs for ids in the block using nodedef
462 // Renumbers the content IDs (starting at 0 and incrementing
463 // use static memory requires about 65535 * sizeof(int) ram in order to be
464 // sure we can handle all content ids. But it's absolutely worth it as it's
465 // a speedup of 4 for one of the major time consuming functions on storing
467 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
468 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
469 INodeDefManager *nodedef)
471 memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
473 std::set<content_t> unknown_contents;
474 content_t id_counter = 0;
475 for (u32 i = 0; i < MapBlock::nodecount; i++) {
476 content_t global_id = nodes[i].getContent();
477 content_t id = CONTENT_IGNORE;
479 // Try to find an existing mapping
480 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
481 id = getBlockNodeIdMapping_mapping[global_id];
485 // We have to assign a new mapping
487 getBlockNodeIdMapping_mapping[global_id] = id;
489 const ContentFeatures &f = nodedef->get(global_id);
490 const std::string &name = f.name;
492 unknown_contents.insert(global_id);
494 nimap->set(id, name);
497 // Update the MapNode
498 nodes[i].setContent(id);
500 for(std::set<content_t>::const_iterator
501 i = unknown_contents.begin();
502 i != unknown_contents.end(); ++i){
503 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
504 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
507 // Correct ids in the block to match nodedef based on names.
508 // Unknown ones are added to nodedef.
509 // Will not update itself to match id-name pairs in nodedef.
510 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
513 INodeDefManager *nodedef = gamedef->ndef();
514 // This means the block contains incorrect ids, and we contain
515 // the information to convert those to names.
516 // nodedef contains information to convert our names to globally
518 std::set<content_t> unnamed_contents;
519 std::set<std::string> unallocatable_contents;
520 for (u32 i = 0; i < MapBlock::nodecount; i++) {
521 content_t local_id = nodes[i].getContent();
523 bool found = nimap->getName(local_id, name);
525 unnamed_contents.insert(local_id);
529 found = nodedef->getId(name, global_id);
531 global_id = gamedef->allocateUnknownNodeId(name);
532 if(global_id == CONTENT_IGNORE){
533 unallocatable_contents.insert(name);
537 nodes[i].setContent(global_id);
539 for(std::set<content_t>::const_iterator
540 i = unnamed_contents.begin();
541 i != unnamed_contents.end(); ++i){
542 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
543 <<"Block contains id "<<(*i)
544 <<" with no name mapping"<<std::endl;
546 for(std::set<std::string>::const_iterator
547 i = unallocatable_contents.begin();
548 i != unallocatable_contents.end(); ++i){
549 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
550 <<"Could not allocate global id for node name \""
551 <<(*i)<<"\""<<std::endl;
555 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
557 if(!ser_ver_supported(version))
558 throw VersionMismatchException("ERROR: MapBlock format not supported");
562 throw SerializationError("ERROR: Not writing dummy block.");
565 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
571 if(getDayNightDiff())
573 if(m_generated == false)
577 writeU16(os, m_lighting_complete);
586 MapNode *tmp_nodes = new MapNode[nodecount];
587 for(u32 i=0; i<nodecount; i++)
588 tmp_nodes[i] = data[i];
589 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
591 u8 content_width = 2;
593 writeU8(os, content_width);
594 writeU8(os, params_width);
595 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
596 content_width, params_width, true);
601 u8 content_width = 2;
603 writeU8(os, content_width);
604 writeU8(os, params_width);
605 MapNode::serializeBulk(os, version, data, nodecount,
606 content_width, params_width, true);
612 std::ostringstream oss(std::ios_base::binary);
613 m_node_metadata.serialize(oss, version, disk);
614 compressZlib(oss.str(), os);
617 Data that goes to disk, but not the network
623 m_node_timers.serialize(os, version);
627 m_static_objects.serialize(os);
630 writeU32(os, getTimestamp());
632 // Write block-specific node definition id mapping
637 m_node_timers.serialize(os, version);
642 void MapBlock::serializeNetworkSpecific(std::ostream &os)
645 throw SerializationError("ERROR: Not writing dummy block.");
648 writeU8(os, 1); // version
649 writeF1000(os, 0); // deprecated heat
650 writeF1000(os, 0); // deprecated humidity
653 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
655 if(!ser_ver_supported(version))
656 throw VersionMismatchException("ERROR: MapBlock format not supported");
658 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
660 m_day_night_differs_expired = false;
664 deSerialize_pre22(is, version, disk);
668 u8 flags = readU8(is);
669 is_underground = (flags & 0x01) ? true : false;
670 m_day_night_differs = (flags & 0x02) ? true : false;
672 m_lighting_complete = 0xFFFF;
674 m_lighting_complete = readU16(is);
675 m_generated = (flags & 0x08) ? false : true;
680 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
681 <<": Bulk node data"<<std::endl);
682 u8 content_width = readU8(is);
683 u8 params_width = readU8(is);
684 if(content_width != 1 && content_width != 2)
685 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
686 if(params_width != 2)
687 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
688 MapNode::deSerializeBulk(is, version, data, nodecount,
689 content_width, params_width, true);
694 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
695 <<": Node metadata"<<std::endl);
698 std::ostringstream oss(std::ios_base::binary);
699 decompressZlib(is, oss);
700 std::istringstream iss(oss.str(), std::ios_base::binary);
702 m_node_metadata.deSerialize(iss, m_gamedef->idef());
704 content_nodemeta_deserialize_legacy(iss,
705 &m_node_metadata, &m_node_timers,
707 } catch(SerializationError &e) {
708 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
709 <<" while deserializing node metadata at ("
710 <<PP(getPos())<<": "<<e.what()<<std::endl;
714 Data that is only on disk
724 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
725 <<": Node timers (ver==24)"<<std::endl);
726 m_node_timers.deSerialize(is, version);
730 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
731 <<": Static objects"<<std::endl);
732 m_static_objects.deSerialize(is);
735 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
736 <<": Timestamp"<<std::endl);
737 setTimestamp(readU32(is));
738 m_disk_timestamp = m_timestamp;
740 // Dynamically re-set ids based on node names
741 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
742 <<": NameIdMapping"<<std::endl);
744 nimap.deSerialize(is);
745 correctBlockNodeIds(&nimap, data, m_gamedef);
748 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
749 <<": Node timers (ver>=25)"<<std::endl);
750 m_node_timers.deSerialize(is, version);
754 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
755 <<": Done."<<std::endl);
758 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
761 int version = readU8(is);
763 // throw SerializationError("unsupported MapBlock version");
765 readF1000(is); // deprecated heat
766 readF1000(is); // deprecated humidity
769 catch(SerializationError &e)
771 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
772 <<": "<<e.what()<<std::endl;
780 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
782 // Initialize default flags
783 is_underground = false;
784 m_day_night_differs = false;
785 m_lighting_complete = 0xFFFF;
788 // Make a temporary buffer
789 u32 ser_length = MapNode::serializedLength(version);
790 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
792 // These have no compression
793 if (version <= 3 || version == 5 || version == 6) {
796 if (is.gcount() != 1)
797 throw SerializationError(std::string(FUNCTION_NAME)
798 + ": not enough input data");
799 is_underground = tmp;
800 is.read((char *)*databuf_nodelist, nodecount * ser_length);
801 if ((u32)is.gcount() != nodecount * ser_length)
802 throw SerializationError(std::string(FUNCTION_NAME)
803 + ": not enough input data");
804 } else if (version <= 10) {
806 is.read((char *)&t8, 1);
810 // Uncompress and set material data
811 std::ostringstream os(std::ios_base::binary);
812 decompress(is, os, version);
813 std::string s = os.str();
814 if (s.size() != nodecount)
815 throw SerializationError(std::string(FUNCTION_NAME)
816 + ": not enough input data");
817 for (u32 i = 0; i < s.size(); i++) {
818 databuf_nodelist[i*ser_length] = s[i];
822 // Uncompress and set param data
823 std::ostringstream os(std::ios_base::binary);
824 decompress(is, os, version);
825 std::string s = os.str();
826 if (s.size() != nodecount)
827 throw SerializationError(std::string(FUNCTION_NAME)
828 + ": not enough input data");
829 for (u32 i = 0; i < s.size(); i++) {
830 databuf_nodelist[i*ser_length + 1] = s[i];
835 // Uncompress and set param2 data
836 std::ostringstream os(std::ios_base::binary);
837 decompress(is, os, version);
838 std::string s = os.str();
839 if (s.size() != nodecount)
840 throw SerializationError(std::string(FUNCTION_NAME)
841 + ": not enough input data");
842 for (u32 i = 0; i < s.size(); i++) {
843 databuf_nodelist[i*ser_length + 2] = s[i];
846 } else { // All other versions (10 to 21)
848 is.read((char*)&flags, 1);
849 is_underground = (flags & 0x01) ? true : false;
850 m_day_night_differs = (flags & 0x02) ? true : false;
852 m_generated = (flags & 0x08) ? false : true;
855 std::ostringstream os(std::ios_base::binary);
856 decompress(is, os, version);
857 std::string s = os.str();
858 if (s.size() != nodecount * 3)
859 throw SerializationError(std::string(FUNCTION_NAME)
860 + ": decompress resulted in size other than nodecount*3");
862 // deserialize nodes from buffer
863 for (u32 i = 0; i < nodecount; i++) {
864 databuf_nodelist[i*ser_length] = s[i];
865 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
866 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
876 std::string data = deSerializeString(is);
877 std::istringstream iss(data, std::ios_base::binary);
878 content_nodemeta_deserialize_legacy(iss,
879 &m_node_metadata, &m_node_timers,
882 //std::string data = deSerializeLongString(is);
883 std::ostringstream oss(std::ios_base::binary);
884 decompressZlib(is, oss);
885 std::istringstream iss(oss.str(), std::ios_base::binary);
886 content_nodemeta_deserialize_legacy(iss,
887 &m_node_metadata, &m_node_timers,
890 } catch(SerializationError &e) {
891 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
892 <<" while deserializing node metadata"<<std::endl;
897 // Deserialize node data
898 for (u32 i = 0; i < nodecount; i++) {
899 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
904 Versions up from 9 have block objects. (DEPRECATED)
907 u16 count = readU16(is);
908 // Not supported and length not known if count is not 0
910 warningstream<<"MapBlock::deSerialize_pre22(): "
911 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
917 Versions up from 15 have static objects.
920 m_static_objects.deSerialize(is);
924 setTimestamp(readU32(is));
925 m_disk_timestamp = m_timestamp;
927 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
930 // Dynamically re-set ids based on node names
932 // If supported, read node definition id mapping
933 nimap.deSerialize(is);
934 correctBlockNodeIds(&nimap, data, m_gamedef);
938 // Legacy data changes
939 // This code has to convert from pre-22 to post-22 format.
940 INodeDefManager *nodedef = m_gamedef->ndef();
941 for(u32 i=0; i<nodecount; i++)
943 const ContentFeatures &f = nodedef->get(data[i].getContent());
945 if(nodedef->getId("default:stone") == data[i].getContent()
946 && data[i].getParam1() == 1)
948 data[i].setContent(nodedef->getId("default:stone_with_coal"));
949 data[i].setParam1(0);
951 else if(nodedef->getId("default:stone") == data[i].getContent()
952 && data[i].getParam1() == 2)
954 data[i].setContent(nodedef->getId("default:stone_with_iron"));
955 data[i].setParam1(0);
958 if(f.legacy_facedir_simple)
960 data[i].setParam2(data[i].getParam1());
961 data[i].setParam1(0);
964 if(f.legacy_wallmounted)
966 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
967 u8 dir_old_format = data[i].getParam2();
968 u8 dir_new_format = 0;
969 for(u8 j=0; j<8; j++)
971 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
977 data[i].setParam2(dir_new_format);
984 Get a quick string to describe what a block actually contains
986 std::string analyze_block(MapBlock *block)
991 std::ostringstream desc;
993 v3s16 p = block->getPos();
995 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
998 switch(block->getModified())
1000 case MOD_STATE_CLEAN:
1003 case MOD_STATE_WRITE_AT_UNLOAD:
1004 desc<<"WRITE_AT_UNLOAD, ";
1006 case MOD_STATE_WRITE_NEEDED:
1007 desc<<"WRITE_NEEDED, ";
1010 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1013 if(block->isGenerated())
1014 desc<<"is_gen [X], ";
1016 desc<<"is_gen [ ], ";
1018 if(block->getIsUnderground())
1019 desc<<"is_ug [X], ";
1021 desc<<"is_ug [ ], ";
1023 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1025 if(block->isDummy())
1031 bool full_ignore = true;
1032 bool some_ignore = false;
1033 bool full_air = true;
1034 bool some_air = false;
1035 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1036 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1037 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1040 MapNode n = block->getNodeNoEx(p);
1041 content_t c = n.getContent();
1042 if(c == CONTENT_IGNORE)
1045 full_ignore = false;
1046 if(c == CONTENT_AIR)
1054 std::ostringstream ss;
1057 ss<<"IGNORE (full), ";
1058 else if(some_ignore)
1066 if(ss.str().size()>=2)
1067 desc<<ss.str().substr(0, ss.str().size()-2);
1072 return desc.str().substr(0, desc.str().size()-2);