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;
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;
497 for (u32 i = 0; i < MapBlock::nodecount; i++) {
498 content_t local_id = nodes[i].getContent();
500 if (!nimap->getName(local_id, name)) {
501 unnamed_contents.insert(local_id);
505 if (!nodedef->getId(name, global_id)) {
506 global_id = gamedef->allocateUnknownNodeId(name);
507 if(global_id == CONTENT_IGNORE){
508 unallocatable_contents.insert(name);
512 nodes[i].setContent(global_id);
515 for (const content_t c: unnamed_contents) {
516 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
517 << "Block contains id " << c
518 << " with no name mapping" << std::endl;
520 for (const std::string &node_name: unallocatable_contents) {
521 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
522 << "Could not allocate global id for node name \""
523 << node_name << "\"" << std::endl;
527 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
529 if(!ser_ver_supported(version))
530 throw VersionMismatchException("ERROR: MapBlock format not supported");
533 throw SerializationError("ERROR: Not writing dummy block.");
535 FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
541 if(getDayNightDiff())
543 if(m_generated == false)
547 writeU16(os, m_lighting_complete);
556 MapNode *tmp_nodes = new MapNode[nodecount];
557 for(u32 i=0; i<nodecount; i++)
558 tmp_nodes[i] = data[i];
559 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
561 u8 content_width = 2;
563 writeU8(os, content_width);
564 writeU8(os, params_width);
565 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
566 content_width, params_width, true);
571 u8 content_width = 2;
573 writeU8(os, content_width);
574 writeU8(os, params_width);
575 MapNode::serializeBulk(os, version, data, nodecount,
576 content_width, params_width, true);
582 std::ostringstream oss(std::ios_base::binary);
583 m_node_metadata.serialize(oss, version, disk);
584 compressZlib(oss.str(), os);
587 Data that goes to disk, but not the network
593 m_node_timers.serialize(os, version);
597 m_static_objects.serialize(os);
600 writeU32(os, getTimestamp());
602 // Write block-specific node definition id mapping
607 m_node_timers.serialize(os, version);
612 void MapBlock::serializeNetworkSpecific(std::ostream &os)
615 throw SerializationError("ERROR: Not writing dummy block.");
618 writeU8(os, 1); // version
619 writeF1000(os, 0); // deprecated heat
620 writeF1000(os, 0); // deprecated humidity
623 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
625 if(!ser_ver_supported(version))
626 throw VersionMismatchException("ERROR: MapBlock format not supported");
628 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
630 m_day_night_differs_expired = false;
634 deSerialize_pre22(is, version, disk);
638 u8 flags = readU8(is);
639 is_underground = (flags & 0x01) ? true : false;
640 m_day_night_differs = (flags & 0x02) ? true : false;
642 m_lighting_complete = 0xFFFF;
644 m_lighting_complete = readU16(is);
645 m_generated = (flags & 0x08) ? false : true;
650 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
651 <<": Bulk node data"<<std::endl);
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->idef());
674 content_nodemeta_deserialize_legacy(iss,
675 &m_node_metadata, &m_node_timers,
677 } catch(SerializationError &e) {
678 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
679 <<" while deserializing node metadata at ("
680 <<PP(getPos())<<": "<<e.what()<<std::endl;
684 Data that is only on disk
694 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
695 <<": Node timers (ver==24)"<<std::endl);
696 m_node_timers.deSerialize(is, version);
700 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
701 <<": Static objects"<<std::endl);
702 m_static_objects.deSerialize(is);
705 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
706 <<": Timestamp"<<std::endl);
707 setTimestamp(readU32(is));
708 m_disk_timestamp = m_timestamp;
710 // Dynamically re-set ids based on node names
711 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
712 <<": NameIdMapping"<<std::endl);
714 nimap.deSerialize(is);
715 correctBlockNodeIds(&nimap, data, m_gamedef);
718 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
719 <<": Node timers (ver>=25)"<<std::endl);
720 m_node_timers.deSerialize(is, version);
724 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
725 <<": Done."<<std::endl);
728 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
731 int version = readU8(is);
733 // throw SerializationError("unsupported MapBlock version");
735 readF1000(is); // deprecated heat
736 readF1000(is); // deprecated humidity
739 catch(SerializationError &e)
741 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
742 <<": "<<e.what()<<std::endl;
750 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
752 // Initialize default flags
753 is_underground = false;
754 m_day_night_differs = false;
755 m_lighting_complete = 0xFFFF;
758 // Make a temporary buffer
759 u32 ser_length = MapNode::serializedLength(version);
760 SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
762 // These have no compression
763 if (version <= 3 || version == 5 || version == 6) {
766 if (is.gcount() != 1)
767 throw SerializationError(std::string(FUNCTION_NAME)
768 + ": not enough input data");
769 is_underground = tmp;
770 is.read((char *)*databuf_nodelist, nodecount * ser_length);
771 if ((u32)is.gcount() != nodecount * ser_length)
772 throw SerializationError(std::string(FUNCTION_NAME)
773 + ": not enough input data");
774 } else if (version <= 10) {
776 is.read((char *)&t8, 1);
780 // Uncompress and set material data
781 std::ostringstream os(std::ios_base::binary);
782 decompress(is, os, version);
783 std::string s = os.str();
784 if (s.size() != nodecount)
785 throw SerializationError(std::string(FUNCTION_NAME)
786 + ": not enough input data");
787 for (u32 i = 0; i < s.size(); i++) {
788 databuf_nodelist[i*ser_length] = s[i];
792 // Uncompress and set param data
793 std::ostringstream os(std::ios_base::binary);
794 decompress(is, os, version);
795 std::string s = os.str();
796 if (s.size() != nodecount)
797 throw SerializationError(std::string(FUNCTION_NAME)
798 + ": not enough input data");
799 for (u32 i = 0; i < s.size(); i++) {
800 databuf_nodelist[i*ser_length + 1] = s[i];
805 // Uncompress and set param2 data
806 std::ostringstream os(std::ios_base::binary);
807 decompress(is, os, version);
808 std::string s = os.str();
809 if (s.size() != nodecount)
810 throw SerializationError(std::string(FUNCTION_NAME)
811 + ": not enough input data");
812 for (u32 i = 0; i < s.size(); i++) {
813 databuf_nodelist[i*ser_length + 2] = s[i];
816 } else { // All other versions (10 to 21)
818 is.read((char*)&flags, 1);
819 is_underground = (flags & 0x01) ? true : false;
820 m_day_night_differs = (flags & 0x02) ? true : false;
822 m_generated = (flags & 0x08) ? false : true;
825 std::ostringstream os(std::ios_base::binary);
826 decompress(is, os, version);
827 std::string s = os.str();
828 if (s.size() != nodecount * 3)
829 throw SerializationError(std::string(FUNCTION_NAME)
830 + ": decompress resulted in size other than nodecount*3");
832 // deserialize nodes from buffer
833 for (u32 i = 0; i < nodecount; i++) {
834 databuf_nodelist[i*ser_length] = s[i];
835 databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
836 databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
846 std::string data = deSerializeString(is);
847 std::istringstream iss(data, std::ios_base::binary);
848 content_nodemeta_deserialize_legacy(iss,
849 &m_node_metadata, &m_node_timers,
852 //std::string data = deSerializeLongString(is);
853 std::ostringstream oss(std::ios_base::binary);
854 decompressZlib(is, oss);
855 std::istringstream iss(oss.str(), std::ios_base::binary);
856 content_nodemeta_deserialize_legacy(iss,
857 &m_node_metadata, &m_node_timers,
860 } catch(SerializationError &e) {
861 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
862 <<" while deserializing node metadata"<<std::endl;
867 // Deserialize node data
868 for (u32 i = 0; i < nodecount; i++) {
869 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
874 Versions up from 9 have block objects. (DEPRECATED)
877 u16 count = readU16(is);
878 // Not supported and length not known if count is not 0
880 warningstream<<"MapBlock::deSerialize_pre22(): "
881 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
887 Versions up from 15 have static objects.
890 m_static_objects.deSerialize(is);
894 setTimestamp(readU32(is));
895 m_disk_timestamp = m_timestamp;
897 setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
900 // Dynamically re-set ids based on node names
902 // If supported, read node definition id mapping
904 nimap.deSerialize(is);
905 // Else set the legacy mapping
907 content_mapnode_get_name_id_mapping(&nimap);
909 correctBlockNodeIds(&nimap, data, m_gamedef);
913 // Legacy data changes
914 // This code has to convert from pre-22 to post-22 format.
915 INodeDefManager *nodedef = m_gamedef->ndef();
916 for(u32 i=0; i<nodecount; i++)
918 const ContentFeatures &f = nodedef->get(data[i].getContent());
920 if(nodedef->getId("default:stone") == data[i].getContent()
921 && data[i].getParam1() == 1)
923 data[i].setContent(nodedef->getId("default:stone_with_coal"));
924 data[i].setParam1(0);
926 else if(nodedef->getId("default:stone") == data[i].getContent()
927 && data[i].getParam1() == 2)
929 data[i].setContent(nodedef->getId("default:stone_with_iron"));
930 data[i].setParam1(0);
933 if(f.legacy_facedir_simple)
935 data[i].setParam2(data[i].getParam1());
936 data[i].setParam1(0);
939 if(f.legacy_wallmounted)
941 u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
942 u8 dir_old_format = data[i].getParam2();
943 u8 dir_new_format = 0;
944 for(u8 j=0; j<8; j++)
946 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
952 data[i].setParam2(dir_new_format);
959 Get a quick string to describe what a block actually contains
961 std::string analyze_block(MapBlock *block)
966 std::ostringstream desc;
968 v3s16 p = block->getPos();
970 snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
973 switch(block->getModified())
975 case MOD_STATE_CLEAN:
978 case MOD_STATE_WRITE_AT_UNLOAD:
979 desc<<"WRITE_AT_UNLOAD, ";
981 case MOD_STATE_WRITE_NEEDED:
982 desc<<"WRITE_NEEDED, ";
985 desc<<"unknown getModified()="+itos(block->getModified())+", ";
988 if(block->isGenerated())
989 desc<<"is_gen [X], ";
991 desc<<"is_gen [ ], ";
993 if(block->getIsUnderground())
998 desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
1000 if(block->isDummy())
1006 bool full_ignore = true;
1007 bool some_ignore = false;
1008 bool full_air = true;
1009 bool some_air = false;
1010 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
1011 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
1012 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
1015 MapNode n = block->getNodeNoEx(p);
1016 content_t c = n.getContent();
1017 if(c == CONTENT_IGNORE)
1020 full_ignore = false;
1021 if(c == CONTENT_AIR)
1029 std::ostringstream ss;
1032 ss<<"IGNORE (full), ";
1033 else if(some_ignore)
1041 if(ss.str().size()>=2)
1042 desc<<ss.str().substr(0, ss.str().size()-2);
1047 return desc.str().substr(0, desc.str().size()-2);