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)
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();
333 // Running this function un-expires m_day_night_differs
334 m_day_night_differs_expired = false;
338 m_day_night_differs = false;
342 bool differs = false;
345 Check if any lighting value differs
347 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
349 MapNode &n = data[i];
350 if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr))
358 If some lighting values differ, check if the whole thing is
359 just air. If it is, differ = false
363 bool only_air = true;
364 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
366 MapNode &n = data[i];
367 if(n.getContent() != CONTENT_AIR)
377 // Set member variable
378 m_day_night_differs = differs;
381 void MapBlock::expireDayNightDiff()
383 //INodeDefManager *nodemgr = m_gamedef->ndef();
386 m_day_night_differs = false;
387 m_day_night_differs_expired = false;
391 m_day_night_differs_expired = true;
394 s16 MapBlock::getGroundLevel(v2s16 p2d)
400 s16 y = MAP_BLOCKSIZE-1;
403 MapNode n = getNodeRef(p2d.X, y, p2d.Y);
404 if(m_gamedef->ndef()->get(n).walkable)
406 if(y == MAP_BLOCKSIZE-1)
414 catch(InvalidPositionException &e)
423 // List relevant id-name pairs for ids in the block using nodedef
424 // Renumbers the content IDs (starting at 0 and incrementing
425 // use static memory requires about 65535 * sizeof(int) ram in order to be
426 // sure we can handle all content ids. But it's absolutely worth it as it's
427 // a speedup of 4 for one of the major time consuming functions on storing
429 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX];
430 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
431 INodeDefManager *nodedef)
433 memset(getBlockNodeIdMapping_mapping, 0xFF, USHRT_MAX * sizeof(content_t));
435 std::set<content_t> unknown_contents;
436 content_t id_counter = 0;
437 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
439 content_t global_id = nodes[i].getContent();
440 content_t id = CONTENT_IGNORE;
442 // Try to find an existing mapping
443 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
444 id = getBlockNodeIdMapping_mapping[global_id];
448 // We have to assign a new mapping
450 getBlockNodeIdMapping_mapping[global_id] = id;
452 const ContentFeatures &f = nodedef->get(global_id);
453 const std::string &name = f.name;
455 unknown_contents.insert(global_id);
457 nimap->set(id, name);
460 // Update the MapNode
461 nodes[i].setContent(id);
463 for(std::set<content_t>::const_iterator
464 i = unknown_contents.begin();
465 i != unknown_contents.end(); i++){
466 errorstream<<"getBlockNodeIdMapping(): IGNORING ERROR: "
467 <<"Name for node id "<<(*i)<<" not known"<<std::endl;
470 // Correct ids in the block to match nodedef based on names.
471 // Unknown ones are added to nodedef.
472 // Will not update itself to match id-name pairs in nodedef.
473 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
476 INodeDefManager *nodedef = gamedef->ndef();
477 // This means the block contains incorrect ids, and we contain
478 // the information to convert those to names.
479 // nodedef contains information to convert our names to globally
481 std::set<content_t> unnamed_contents;
482 std::set<std::string> unallocatable_contents;
483 for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
485 content_t local_id = nodes[i].getContent();
487 bool found = nimap->getName(local_id, name);
489 unnamed_contents.insert(local_id);
493 found = nodedef->getId(name, global_id);
495 global_id = gamedef->allocateUnknownNodeId(name);
496 if(global_id == CONTENT_IGNORE){
497 unallocatable_contents.insert(name);
501 nodes[i].setContent(global_id);
503 for(std::set<content_t>::const_iterator
504 i = unnamed_contents.begin();
505 i != unnamed_contents.end(); i++){
506 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
507 <<"Block contains id "<<(*i)
508 <<" with no name mapping"<<std::endl;
510 for(std::set<std::string>::const_iterator
511 i = unallocatable_contents.begin();
512 i != unallocatable_contents.end(); i++){
513 errorstream<<"correctBlockNodeIds(): IGNORING ERROR: "
514 <<"Could not allocate global id for node name \""
515 <<(*i)<<"\""<<std::endl;
519 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
521 if(!ser_ver_supported(version))
522 throw VersionMismatchException("ERROR: MapBlock format not supported");
526 throw SerializationError("ERROR: Not writing dummy block.");
529 // Can't do this anymore; we have 16-bit dynamically allocated node IDs
530 // in memory; conversion just won't work in this direction.
532 throw SerializationError("MapBlock::serialize: serialization to "
533 "version < 24 not possible");
539 if(getDayNightDiff())
541 if(m_lighting_expired)
543 if(m_generated == false)
551 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
554 MapNode *tmp_nodes = new MapNode[nodecount];
555 for(u32 i=0; i<nodecount; i++)
556 tmp_nodes[i] = data[i];
557 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
559 u8 content_width = 2;
561 writeU8(os, content_width);
562 writeU8(os, params_width);
563 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
564 content_width, params_width, true);
569 u8 content_width = 2;
571 writeU8(os, content_width);
572 writeU8(os, params_width);
573 MapNode::serializeBulk(os, version, data, nodecount,
574 content_width, params_width, true);
580 std::ostringstream oss(std::ios_base::binary);
581 m_node_metadata.serialize(oss);
582 compressZlib(oss.str(), os);
585 Data that goes to disk, but not the network
591 m_node_timers.serialize(os, version);
595 m_static_objects.serialize(os);
598 writeU32(os, getTimestamp());
600 // Write block-specific node definition id mapping
605 m_node_timers.serialize(os, version);
610 void MapBlock::serializeNetworkSpecific(std::ostream &os, u16 net_proto_version)
614 throw SerializationError("ERROR: Not writing dummy block.");
617 if(net_proto_version >= 21){
619 writeU8(os, version);
620 writeF1000(os, 0); // deprecated heat
621 writeF1000(os, 0); // deprecated humidity
625 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
627 if(!ser_ver_supported(version))
628 throw VersionMismatchException("ERROR: MapBlock format not supported");
630 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
632 m_day_night_differs_expired = false;
636 deSerialize_pre22(is, version, disk);
640 u8 flags = readU8(is);
641 is_underground = (flags & 0x01) ? true : false;
642 m_day_night_differs = (flags & 0x02) ? true : false;
643 m_lighting_expired = (flags & 0x04) ? true : false;
644 m_generated = (flags & 0x08) ? false : true;
649 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
650 <<": Bulk node data"<<std::endl);
651 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
652 u8 content_width = readU8(is);
653 u8 params_width = readU8(is);
654 if(content_width != 1 && content_width != 2)
655 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
656 if(params_width != 2)
657 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
658 MapNode::deSerializeBulk(is, version, data, nodecount,
659 content_width, params_width, true);
664 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
665 <<": Node metadata"<<std::endl);
668 std::ostringstream oss(std::ios_base::binary);
669 decompressZlib(is, oss);
670 std::istringstream iss(oss.str(), std::ios_base::binary);
672 m_node_metadata.deSerialize(iss, m_gamedef);
674 content_nodemeta_deserialize_legacy(iss,
675 &m_node_metadata, &m_node_timers,
678 catch(SerializationError &e)
680 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
681 <<" while deserializing node metadata at ("
682 <<PP(getPos())<<": "<<e.what()<<std::endl;
686 Data that is only on disk
696 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
697 <<": Node timers (ver==24)"<<std::endl);
698 m_node_timers.deSerialize(is, version);
702 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
703 <<": Static objects"<<std::endl);
704 m_static_objects.deSerialize(is);
707 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
708 <<": Timestamp"<<std::endl);
709 setTimestamp(readU32(is));
710 m_disk_timestamp = m_timestamp;
712 // Dynamically re-set ids based on node names
713 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
714 <<": NameIdMapping"<<std::endl);
716 nimap.deSerialize(is);
717 correctBlockNodeIds(&nimap, data, m_gamedef);
720 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
721 <<": Node timers (ver>=25)"<<std::endl);
722 m_node_timers.deSerialize(is, version);
726 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
727 <<": Done."<<std::endl);
730 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
733 int version = readU8(is);
735 // throw SerializationError("unsupported MapBlock version");
737 readF1000(is); // deprecated heat
738 readF1000(is); // deprecated humidity
741 catch(SerializationError &e)
743 errorstream<<"WARNING: MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
744 <<": "<<e.what()<<std::endl;
752 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
754 u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
756 // Initialize default flags
757 is_underground = false;
758 m_day_night_differs = false;
759 m_lighting_expired = false;
762 // Make a temporary buffer
763 u32 ser_length = MapNode::serializedLength(version);
764 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
766 // These have no compression
767 if(version <= 3 || version == 5 || version == 6)
772 throw SerializationError
773 ("MapBlock::deSerialize: no enough input data");
774 is_underground = tmp;
775 is.read((char*)*databuf_nodelist, nodecount * ser_length);
776 if((u32)is.gcount() != nodecount * ser_length)
777 throw SerializationError
778 ("MapBlock::deSerialize: no enough input data");
780 else if(version <= 10)
783 is.read((char*)&t8, 1);
787 // Uncompress and set material data
788 std::ostringstream os(std::ios_base::binary);
789 decompress(is, os, version);
790 std::string s = os.str();
791 if(s.size() != nodecount)
792 throw SerializationError
793 ("MapBlock::deSerialize: invalid format");
794 for(u32 i=0; i<s.size(); i++)
796 databuf_nodelist[i*ser_length] = s[i];
800 // Uncompress and set param data
801 std::ostringstream os(std::ios_base::binary);
802 decompress(is, os, version);
803 std::string s = os.str();
804 if(s.size() != nodecount)
805 throw SerializationError
806 ("MapBlock::deSerialize: invalid format");
807 for(u32 i=0; i<s.size(); i++)
809 databuf_nodelist[i*ser_length + 1] = s[i];
815 // Uncompress and set param2 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
821 ("MapBlock::deSerialize: invalid format");
822 for(u32 i=0; i<s.size(); i++)
824 databuf_nodelist[i*ser_length + 2] = s[i];
828 // All other versions (newest)
832 is.read((char*)&flags, 1);
833 is_underground = (flags & 0x01) ? true : false;
834 m_day_night_differs = (flags & 0x02) ? true : false;
835 m_lighting_expired = (flags & 0x04) ? true : false;
837 m_generated = (flags & 0x08) ? false : true;
840 std::ostringstream os(std::ios_base::binary);
841 decompress(is, os, version);
842 std::string s = os.str();
843 if(s.size() != nodecount*3)
844 throw SerializationError
845 ("MapBlock::deSerialize: decompress resulted in size"
846 " other than nodecount*3");
848 // deserialize nodes from buffer
849 for(u32 i=0; i<nodecount; i++)
851 databuf_nodelist[i*ser_length] = s[i];
852 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
853 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
865 std::string data = deSerializeString(is);
866 std::istringstream iss(data, std::ios_base::binary);
867 content_nodemeta_deserialize_legacy(iss,
868 &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,
882 catch(SerializationError &e)
884 errorstream<<"WARNING: MapBlock::deSerialize(): Ignoring an error"
885 <<" while deserializing node metadata"<<std::endl;
890 // Deserialize node data
891 for(u32 i=0; i<nodecount; i++)
893 data[i].deSerialize(&databuf_nodelist[i*ser_length], version);
899 Versions up from 9 have block objects. (DEPRECATED)
902 u16 count = readU16(is);
903 // Not supported and length not known if count is not 0
905 errorstream<<"WARNING: MapBlock::deSerialize_pre22(): "
906 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
912 Versions up from 15 have static objects.
915 m_static_objects.deSerialize(is);
919 setTimestamp(readU32(is));
920 m_disk_timestamp = m_timestamp;
922 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
925 // Dynamically re-set ids based on node names
927 // If supported, read node definition id mapping
929 nimap.deSerialize(is);
930 // Else set the legacy mapping
932 content_mapnode_get_name_id_mapping(&nimap);
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 if(block->getLightingExpired())
1024 desc<<"lighting_exp [X], ";
1026 desc<<"lighting_exp [ ], ";
1028 if(block->isDummy())
1034 bool full_ignore = true;
1035 bool some_ignore = false;
1036 bool full_air = true;
1037 bool some_air = false;
1038 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1039 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1040 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1043 MapNode n = block->getNodeNoEx(p);
1044 content_t c = n.getContent();
1045 if(c == CONTENT_IGNORE)
1048 full_ignore = false;
1049 if(c == CONTENT_AIR)
1057 std::ostringstream ss;
1060 ss<<"IGNORE (full), ";
1061 else if(some_ignore)
1069 if(ss.str().size()>=2)
1070 desc<<ss.str().substr(0, ss.str().size()-2);
1075 return desc.str().substr(0, desc.str().size()-2);