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"
39 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
45 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
49 m_modified(MOD_STATE_WRITE_NEEDED),
50 m_modified_reason("initial"),
51 m_modified_reason_too_long(false),
52 is_underground(false),
53 m_lighting_expired(true),
54 m_day_night_differs(false),
55 m_day_night_differs_expired(true),
57 m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
58 m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
75 //JMutexAutoLock lock(mesh_mutex);
89 bool MapBlock::isValidPositionParent(v3s16 p)
91 if(isValidPosition(p))
96 return m_parent->isValidPosition(getPosRelative() + p);
100 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
102 if (isValidPosition(p) == false)
103 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
106 if (is_valid_position)
107 *is_valid_position = false;
108 return MapNode(CONTENT_IGNORE);
110 if (is_valid_position)
111 *is_valid_position = true;
112 return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X];
116 Propagates sunlight down through the block.
117 Doesn't modify nodes that are not affected by sunlight.
119 Returns false if sunlight at bottom block is invalid.
120 Returns true if sunlight at bottom block is valid.
121 Returns true if bottom block doesn't exist.
123 If there is a block above, continues from it.
124 If there is no block above, assumes there is sunlight, unless
125 is_underground is set or highest node is water.
127 All sunlighted nodes are added to light_sources.
129 if remove_light==true, sets non-sunlighted nodes black.
131 if black_air_left!=NULL, it is set to true if non-sunlighted
132 air is left in block.
134 bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources,
135 bool remove_light, bool *black_air_left)
137 INodeDefManager *nodemgr = m_gamedef->ndef();
139 // Whether the sunlight at the top of the bottom block is valid
140 bool block_below_is_valid = true;
142 v3s16 pos_relative = getPosRelative();
144 for(s16 x=0; x<MAP_BLOCKSIZE; x++)
146 for(s16 z=0; z<MAP_BLOCKSIZE; z++)
149 bool no_sunlight = false;
150 //bool no_top_block = false;
152 // Check if node above block has sunlight
154 bool is_valid_position;
155 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z),
157 if (is_valid_position)
159 if(n.getContent() == CONTENT_IGNORE)
162 no_sunlight = is_underground;
164 else if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN)
171 //no_top_block = true;
173 // NOTE: This makes over-ground roofed places sunlighted
174 // Assume sunlight, unless is_underground==true
181 MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z));
182 if(m_gamedef->ndef()->get(n).sunlight_propagates == false)
187 // NOTE: As of now, this just would make everything dark.
189 //no_sunlight = true;
192 #if 0 // Doesn't work; nothing gets light.
193 bool no_sunlight = true;
194 bool no_top_block = false;
195 // Check if node above block has sunlight
197 MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z));
198 if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN)
203 catch(InvalidPositionException &e)
209 /*std::cout<<"("<<x<<","<<z<<"): "
210 <<"no_top_block="<<no_top_block
211 <<", is_underground="<<is_underground
212 <<", no_sunlight="<<no_sunlight
215 s16 y = MAP_BLOCKSIZE-1;
217 // This makes difference to diminishing in water.
218 bool stopped_to_solid_object = false;
220 u8 current_light = no_sunlight ? 0 : LIGHT_SUN;
225 MapNode &n = getNodeRef(pos);
227 if(current_light == 0)
231 else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates)
233 // Do nothing: Sunlight is continued
235 else if(nodemgr->get(n).light_propagates == false)
237 // A solid object is on the way.
238 stopped_to_solid_object = true;
246 current_light = diminish_light(current_light);
249 u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr);
251 if(current_light > old_light || remove_light)
253 n.setLight(LIGHTBANK_DAY, current_light, nodemgr);
256 if(diminish_light(current_light) != 0)
258 light_sources.insert(pos_relative + pos);
261 if(current_light == 0 && stopped_to_solid_object)
265 *black_air_left = true;
270 // Whether or not the block below should see LIGHT_SUN
271 bool sunlight_should_go_down = (current_light == LIGHT_SUN);
274 If the block below hasn't already been marked invalid:
276 Check if the node below the block has proper sunlight at top.
277 If not, the block below is invalid.
279 Ignore non-transparent nodes as they always have no light
282 if(block_below_is_valid)
284 MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position);
285 if (is_valid_position) {
286 if(nodemgr->get(n).light_propagates)
288 if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN
289 && sunlight_should_go_down == false)
290 block_below_is_valid = false;
291 else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN
292 && sunlight_should_go_down == true)
293 block_below_is_valid = false;
298 /*std::cout<<"InvalidBlockException for bottom block node"
300 // Just no block below, no need to panic.
306 return block_below_is_valid;
310 void MapBlock::copyTo(VoxelManipulator &dst)
312 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
313 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
315 // Copy from data to VoxelManipulator
316 dst.copyFrom(data, data_area, v3s16(0,0,0),
317 getPosRelative(), data_size);
320 void MapBlock::copyFrom(VoxelManipulator &dst)
322 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
323 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
325 // Copy from VoxelManipulator to data
326 dst.copyTo(data, data_area, v3s16(0,0,0),
327 getPosRelative(), data_size);
330 void MapBlock::actuallyUpdateDayNightDiff()
332 INodeDefManager *nodemgr = m_gamedef->ndef();
334 // Running this function un-expires m_day_night_differs
335 m_day_night_differs_expired = false;
338 m_day_night_differs = false;
345 Check if any lighting value differs
347 for (u32 i = 0; i < MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++) {
348 MapNode &n = data[i];
350 differs = !n.isLightDayNightEq(nodemgr);
356 If some lighting values differ, check if the whole thing is
357 just air. If it is just air, differs = false
360 bool only_air = true;
361 for (u32 i = 0; i < MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++) {
362 MapNode &n = data[i];
363 if (n.getContent() != CONTENT_AIR) {
372 // Set member variable
373 m_day_night_differs = differs;
376 void MapBlock::expireDayNightDiff()
378 //INodeDefManager *nodemgr = m_gamedef->ndef();
381 m_day_night_differs = false;
382 m_day_night_differs_expired = false;
386 m_day_night_differs_expired = true;
389 s16 MapBlock::getGroundLevel(v2s16 p2d)
395 s16 y = MAP_BLOCKSIZE-1;
398 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
399 if(m_gamedef->ndef()->get(n).walkable)
401 if(y == MAP_BLOCKSIZE-1)
409 catch(InvalidPositionException &e)
418 // List relevant id-name pairs for ids in the block using nodedef
419 // Renumbers the content IDs (starting at 0 and incrementing
420 // use static memory requires about 65535 * sizeof(int) ram in order to be
421 // sure we can handle all content ids. But it's absolutely worth it as it's
422 // a speedup of 4 for one of the major time consuming functions on storing
424 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX];
425 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
426 INodeDefManager *nodedef)
428 memset(getBlockNodeIdMapping_mapping, 0xFF, USHRT_MAX * sizeof(content_t));
430 std::set<content_t> unknown_contents;
431 content_t id_counter = 0;
432 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
434 content_t global_id = nodes[i].getContent();
435 content_t id = CONTENT_IGNORE;
437 // Try to find an existing mapping
438 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
439 id = getBlockNodeIdMapping_mapping[global_id];
443 // We have to assign a new mapping
445 getBlockNodeIdMapping_mapping[global_id] = id;
447 const ContentFeatures &f = nodedef->get(global_id);
448 const std::string &name = f.name;
450 unknown_contents.insert(global_id);
452 nimap->set(id, name);
455 // Update the MapNode
456 nodes[i].setContent(id);
458 for(std::set<content_t>::const_iterator
459 i = unknown_contents.begin();
460 i != unknown_contents.end(); i++){
461 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
462 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
465 // Correct ids in the block to match nodedef based on names.
466 // Unknown ones are added to nodedef.
467 // Will not update itself to match id-name pairs in nodedef.
468 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
471 INodeDefManager *nodedef = gamedef->ndef();
472 // This means the block contains incorrect ids, and we contain
473 // the information to convert those to names.
474 // nodedef contains information to convert our names to globally
476 std::set<content_t> unnamed_contents;
477 std::set<std::string> unallocatable_contents;
478 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
480 content_t local_id = nodes[i].getContent();
482 bool found = nimap->getName(local_id, name);
484 unnamed_contents.insert(local_id);
488 found = nodedef->getId(name, global_id);
490 global_id = gamedef->allocateUnknownNodeId(name);
491 if(global_id == CONTENT_IGNORE){
492 unallocatable_contents.insert(name);
496 nodes[i].setContent(global_id);
498 for(std::set<content_t>::const_iterator
499 i = unnamed_contents.begin();
500 i != unnamed_contents.end(); i++){
501 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
502 <<"Block contains id "<<(*i)
503 <<" with no name mapping"<<std::endl;
505 for(std::set<std::string>::const_iterator
506 i = unallocatable_contents.begin();
507 i != unallocatable_contents.end(); i++){
508 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
509 <<"Could not allocate global id for node name \""
510 <<(*i)<<"\""<<std::endl;
514 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
516 if(!ser_ver_supported(version))
517 throw VersionMismatchException("ERROR: MapBlock format not supported");
521 throw SerializationError("ERROR: Not writing dummy block.");
524 assert(version >= SER_FMT_CLIENT_VER_LOWEST);
530 if(getDayNightDiff())
532 if(m_lighting_expired)
534 if(m_generated == false)
542 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
545 MapNode *tmp_nodes = new MapNode[nodecount];
546 for(u32 i=0; i<nodecount; i++)
547 tmp_nodes[i] = data[i];
548 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
550 u8 content_width = 2;
552 writeU8(os, content_width);
553 writeU8(os, params_width);
554 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
555 content_width, params_width, true);
560 u8 content_width = 2;
562 writeU8(os, content_width);
563 writeU8(os, params_width);
564 MapNode::serializeBulk(os, version, data, nodecount,
565 content_width, params_width, true);
571 std::ostringstream oss(std::ios_base::binary);
572 m_node_metadata.serialize(oss);
573 compressZlib(oss.str(), os);
576 Data that goes to disk, but not the network
582 m_node_timers.serialize(os, version);
586 m_static_objects.serialize(os);
589 writeU32(os, getTimestamp());
591 // Write block-specific node definition id mapping
596 m_node_timers.serialize(os, version);
601 void MapBlock::serializeNetworkSpecific(std::ostream &os, u16 net_proto_version)
605 throw SerializationError("ERROR: Not writing dummy block.");
608 if(net_proto_version >= 21){
610 writeU8(os, version);
611 writeF1000(os, 0); // deprecated heat
612 writeF1000(os, 0); // deprecated humidity
616 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
618 if(!ser_ver_supported(version))
619 throw VersionMismatchException("ERROR: MapBlock format not supported");
621 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
623 m_day_night_differs_expired = false;
627 deSerialize_pre22(is, version, disk);
631 u8 flags = readU8(is);
632 is_underground = (flags & 0x01) ? true : false;
633 m_day_night_differs = (flags & 0x02) ? true : false;
634 m_lighting_expired = (flags & 0x04) ? true : false;
635 m_generated = (flags & 0x08) ? false : true;
640 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
641 <<": Bulk node data"<<std::endl);
642 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
643 u8 content_width = readU8(is);
644 u8 params_width = readU8(is);
645 if(content_width != 1 && content_width != 2)
646 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
647 if(params_width != 2)
648 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
649 MapNode::deSerializeBulk(is, version, data, nodecount,
650 content_width, params_width, true);
655 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
656 <<": Node metadata"<<std::endl);
659 std::ostringstream oss(std::ios_base::binary);
660 decompressZlib(is, oss);
661 std::istringstream iss(oss.str(), std::ios_base::binary);
663 m_node_metadata.deSerialize(iss, m_gamedef);
665 content_nodemeta_deserialize_legacy(iss,
666 &m_node_metadata, &m_node_timers,
669 catch(SerializationError &e)
671 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
672 <<" while deserializing node metadata at ("
673 <<PP(getPos())<<": "<<e.what()<<std::endl;
677 Data that is only on disk
687 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
688 <<": Node timers (ver==24)"<<std::endl);
689 m_node_timers.deSerialize(is, version);
693 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
694 <<": Static objects"<<std::endl);
695 m_static_objects.deSerialize(is);
698 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
699 <<": Timestamp"<<std::endl);
700 setTimestamp(readU32(is));
701 m_disk_timestamp = m_timestamp;
703 // Dynamically re-set ids based on node names
704 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
705 <<": NameIdMapping"<<std::endl);
707 nimap.deSerialize(is);
708 correctBlockNodeIds(&nimap, data, m_gamedef);
711 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
712 <<": Node timers (ver>=25)"<<std::endl);
713 m_node_timers.deSerialize(is, version);
717 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
718 <<": Done."<<std::endl);
721 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
724 int version = readU8(is);
726 // throw SerializationError("unsupported MapBlock version");
728 readF1000(is); // deprecated heat
729 readF1000(is); // deprecated humidity
732 catch(SerializationError &e)
734 errorstream<<"WARNING: MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
735 <<": "<<e.what()<<std::endl;
743 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
745 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
747 // Initialize default flags
748 is_underground = false;
749 m_day_night_differs = false;
750 m_lighting_expired = false;
753 // Make a temporary buffer
754 u32 ser_length = MapNode::serializedLength(version);
755 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
757 // These have no compression
758 if(version <= 3 || version == 5 || version == 6)
763 throw SerializationError
764 ("MapBlock::deSerialize: no enough input data");
765 is_underground = tmp;
766 is.read((char*)*databuf_nodelist, nodecount * ser_length);
767 if((u32)is.gcount() != nodecount * ser_length)
768 throw SerializationError
769 ("MapBlock::deSerialize: no enough input data");
771 else if(version <= 10)
774 is.read((char*)&t8, 1);
778 // Uncompress and set material data
779 std::ostringstream os(std::ios_base::binary);
780 decompress(is, os, version);
781 std::string s = os.str();
782 if(s.size() != nodecount)
783 throw SerializationError
784 ("MapBlock::deSerialize: invalid format");
785 for(u32 i=0; i<s.size(); i++)
787 databuf_nodelist[i*ser_length] = s[i];
791 // Uncompress and set param data
792 std::ostringstream os(std::ios_base::binary);
793 decompress(is, os, version);
794 std::string s = os.str();
795 if(s.size() != nodecount)
796 throw SerializationError
797 ("MapBlock::deSerialize: invalid format");
798 for(u32 i=0; i<s.size(); i++)
800 databuf_nodelist[i*ser_length + 1] = s[i];
806 // Uncompress and set param2 data
807 std::ostringstream os(std::ios_base::binary);
808 decompress(is, os, version);
809 std::string s = os.str();
810 if(s.size() != nodecount)
811 throw SerializationError
812 ("MapBlock::deSerialize: invalid format");
813 for(u32 i=0; i<s.size(); i++)
815 databuf_nodelist[i*ser_length + 2] = s[i];
819 // All other versions (newest)
823 is.read((char*)&flags, 1);
824 is_underground = (flags & 0x01) ? true : false;
825 m_day_night_differs = (flags & 0x02) ? true : false;
826 m_lighting_expired = (flags & 0x04) ? true : false;
828 m_generated = (flags & 0x08) ? false : true;
831 std::ostringstream os(std::ios_base::binary);
832 decompress(is, os, version);
833 std::string s = os.str();
834 if(s.size() != nodecount*3)
835 throw SerializationError
836 ("MapBlock::deSerialize: decompress resulted in size"
837 " other than nodecount*3");
839 // deserialize nodes from buffer
840 for(u32 i=0; i<nodecount; i++)
842 databuf_nodelist[i*ser_length] = s[i];
843 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
844 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
856 std::string data = deSerializeString(is);
857 std::istringstream iss(data, std::ios_base::binary);
858 content_nodemeta_deserialize_legacy(iss,
859 &m_node_metadata, &m_node_timers,
864 //std::string data = deSerializeLongString(is);
865 std::ostringstream oss(std::ios_base::binary);
866 decompressZlib(is, oss);
867 std::istringstream iss(oss.str(), std::ios_base::binary);
868 content_nodemeta_deserialize_legacy(iss,
869 &m_node_metadata, &m_node_timers,
873 catch(SerializationError &e)
875 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
876 <<" while deserializing node metadata"<<std::endl;
881 // Deserialize node data
882 for(u32 i=0; i<nodecount; i++)
884 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
890 Versions up from 9 have block objects. (DEPRECATED)
893 u16 count = readU16(is);
894 // Not supported and length not known if count is not 0
896 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
897 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
903 Versions up from 15 have static objects.
906 m_static_objects.deSerialize(is);
910 setTimestamp(readU32(is));
911 m_disk_timestamp = m_timestamp;
913 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
916 // Dynamically re-set ids based on node names
918 // If supported, read node definition id mapping
920 nimap.deSerialize(is);
921 // Else set the legacy mapping
923 content_mapnode_get_name_id_mapping(&nimap);
925 correctBlockNodeIds(&nimap, data, m_gamedef);
929 // Legacy data changes
930 // This code has to convert from pre-22 to post-22 format.
931 INodeDefManager *nodedef = m_gamedef->ndef();
932 for(u32 i=0; i<nodecount; i++)
934 const ContentFeatures &f = nodedef->get(data[i].getContent());
936 if(nodedef->getId("default:stone") == data[i].getContent()
937 && data[i].getParam1() == 1)
939 data[i].setContent(nodedef->getId("default:stone_with_coal"));
940 data[i].setParam1(0);
942 else if(nodedef->getId("default:stone") == data[i].getContent()
943 && data[i].getParam1() == 2)
945 data[i].setContent(nodedef->getId("default:stone_with_iron"));
946 data[i].setParam1(0);
949 if(f.legacy_facedir_simple)
951 data[i].setParam2(data[i].getParam1());
952 data[i].setParam1(0);
955 if(f.legacy_wallmounted)
957 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
958 u8 dir_old_format = data[i].getParam2();
959 u8 dir_new_format = 0;
960 for(u8 j=0; j<8; j++)
962 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
968 data[i].setParam2(dir_new_format);
975 Get a quick string to describe what a block actually contains
977 std::string analyze_block(MapBlock *block)
982 std::ostringstream desc;
984 v3s16 p = block->getPos();
986 snprintf(spos, 20, "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
989 switch(block->getModified())
991 case MOD_STATE_CLEAN:
994 case MOD_STATE_WRITE_AT_UNLOAD:
995 desc<<"WRITE_AT_UNLOAD, ";
997 case MOD_STATE_WRITE_NEEDED:
998 desc<<"WRITE_NEEDED, ";
1001 desc<<"unknown getModified()="+itos(block->getModified())+", ";
1004 if(block->isGenerated())
1005 desc<<"is_gen [X], ";
1007 desc<<"is_gen [ ], ";
1009 if(block->getIsUnderground())
1010 desc<<"is_ug [X], ";
1012 desc<<"is_ug [ ], ";
1014 if(block->getLightingExpired())
1015 desc<<"lighting_exp [X], ";
1017 desc<<"lighting_exp [ ], ";
1019 if(block->isDummy())
1025 bool full_ignore = true;
1026 bool some_ignore = false;
1027 bool full_air = true;
1028 bool some_air = false;
1029 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1030 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1031 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1034 MapNode n = block->getNodeNoEx(p);
1035 content_t c = n.getContent();
1036 if(c == CONTENT_IGNORE)
1039 full_ignore = false;
1040 if(c == CONTENT_AIR)
1048 std::ostringstream ss;
1051 ss<<"IGNORE (full), ";
1052 else if(some_ignore)
1060 if(ss.str().size()>=2)
1061 desc<<ss.str().substr(0, ss.str().size()-2);
1066 return desc.str().substr(0, desc.str().size()-2);