Spawn level: Add 'get_spawn_level(x, z)' API
[oweals/minetest.git] / src / mapblock.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "mapblock.h"
21
22 #include <sstream>
23 #include "map.h"
24 #include "light.h"
25 #include "nodedef.h"
26 #include "nodemetadata.h"
27 #include "gamedef.h"
28 #include "log.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"
33 #ifndef SERVER
34 #include "mapblock_mesh.h"
35 #endif
36 #include "util/string.h"
37 #include "util/serialize.h"
38 #include "util/basic_macros.h"
39
40 static const char *modified_reason_strings[] = {
41         "initial",
42         "reallocate",
43         "setIsUnderground",
44         "setLightingExpired",
45         "setGenerated",
46         "setNode",
47         "setNodeNoCheck",
48         "setTimestamp",
49         "NodeMetaRef::reportMetadataChange",
50         "clearAllObjects",
51         "Timestamp expired (step)",
52         "addActiveObjectRaw",
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",
60         "unknown",
61 };
62
63
64 /*
65         MapBlock
66 */
67
68 MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
69                 m_parent(parent),
70                 m_pos(pos),
71                 m_pos_relative(pos * MAP_BLOCKSIZE),
72                 m_gamedef(gamedef)
73 {
74         if (!dummy)
75                 reallocate();
76 }
77
78 MapBlock::~MapBlock()
79 {
80 #ifndef SERVER
81         {
82                 delete mesh;
83                 mesh = nullptr;
84         }
85 #endif
86
87         delete[] data;
88 }
89
90 bool MapBlock::isValidPositionParent(v3s16 p)
91 {
92         if (isValidPosition(p)) {
93                 return true;
94         }
95
96         return m_parent->isValidPosition(getPosRelative() + p);
97 }
98
99 MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
100 {
101         if (!isValidPosition(p))
102                 return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
103
104         if (!data) {
105                 if (is_valid_position)
106                         *is_valid_position = false;
107                 return {CONTENT_IGNORE};
108         }
109         if (is_valid_position)
110                 *is_valid_position = true;
111         return data[p.Z * zstride + p.Y * ystride + p.X];
112 }
113
114 std::string MapBlock::getModifiedReasonString()
115 {
116         std::string reason;
117
118         const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT,
119                 ARRLEN(modified_reason_strings));
120
121         for (u32 i = 0; i != ubound; i++) {
122                 if ((m_modified_reason & (1 << i)) == 0)
123                         continue;
124
125                 reason += modified_reason_strings[i];
126                 reason += ", ";
127         }
128
129         if (reason.length() > 2)
130                 reason.resize(reason.length() - 2);
131
132         return reason;
133 }
134
135
136 void MapBlock::copyTo(VoxelManipulator &dst)
137 {
138         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
139         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
140
141         // Copy from data to VoxelManipulator
142         dst.copyFrom(data, data_area, v3s16(0,0,0),
143                         getPosRelative(), data_size);
144 }
145
146 void MapBlock::copyFrom(VoxelManipulator &dst)
147 {
148         v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
149         VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
150
151         // Copy from VoxelManipulator to data
152         dst.copyTo(data, data_area, v3s16(0,0,0),
153                         getPosRelative(), data_size);
154 }
155
156 void MapBlock::actuallyUpdateDayNightDiff()
157 {
158         const NodeDefManager *nodemgr = m_gamedef->ndef();
159
160         // Running this function un-expires m_day_night_differs
161         m_day_night_differs_expired = false;
162
163         if (!data) {
164                 m_day_night_differs = false;
165                 return;
166         }
167
168         bool differs = false;
169
170         /*
171                 Check if any lighting value differs
172         */
173
174         MapNode previous_n(CONTENT_IGNORE);
175         for (u32 i = 0; i < nodecount; i++) {
176                 MapNode n = data[i];
177
178                 // If node is identical to previous node, don't verify if it differs
179                 if (n == previous_n)
180                         continue;
181
182                 differs = !n.isLightDayNightEq(nodemgr);
183                 if (differs)
184                         break;
185                 previous_n = n;
186         }
187
188         /*
189                 If some lighting values differ, check if the whole thing is
190                 just air. If it is just air, differs = false
191         */
192         if (differs) {
193                 bool only_air = true;
194                 for (u32 i = 0; i < nodecount; i++) {
195                         MapNode &n = data[i];
196                         if (n.getContent() != CONTENT_AIR) {
197                                 only_air = false;
198                                 break;
199                         }
200                 }
201                 if (only_air)
202                         differs = false;
203         }
204
205         // Set member variable
206         m_day_night_differs = differs;
207 }
208
209 void MapBlock::expireDayNightDiff()
210 {
211         if (!data) {
212                 m_day_night_differs = false;
213                 m_day_night_differs_expired = false;
214                 return;
215         }
216
217         m_day_night_differs_expired = true;
218 }
219
220 s16 MapBlock::getGroundLevel(v2s16 p2d)
221 {
222         if(isDummy())
223                 return -3;
224         try
225         {
226                 s16 y = MAP_BLOCKSIZE-1;
227                 for(; y>=0; y--)
228                 {
229                         MapNode n = getNodeRef(p2d.X, y, p2d.Y);
230                         if (m_gamedef->ndef()->get(n).walkable) {
231                                 if(y == MAP_BLOCKSIZE-1)
232                                         return -2;
233
234                                 return y;
235                         }
236                 }
237                 return -1;
238         }
239         catch(InvalidPositionException &e)
240         {
241                 return -3;
242         }
243 }
244
245 /*
246         Serialization
247 */
248 // List relevant id-name pairs for ids in the block using nodedef
249 // Renumbers the content IDs (starting at 0 and incrementing
250 // use static memory requires about 65535 * sizeof(int) ram in order to be
251 // sure we can handle all content ids. But it's absolutely worth it as it's
252 // a speedup of 4 for one of the major time consuming functions on storing
253 // mapblocks.
254 static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
255 static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
256         const NodeDefManager *nodedef)
257 {
258         memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
259
260         std::set<content_t> unknown_contents;
261         content_t id_counter = 0;
262         for (u32 i = 0; i < MapBlock::nodecount; i++) {
263                 content_t global_id = nodes[i].getContent();
264                 content_t id = CONTENT_IGNORE;
265
266                 // Try to find an existing mapping
267                 if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
268                         id = getBlockNodeIdMapping_mapping[global_id];
269                 }
270                 else
271                 {
272                         // We have to assign a new mapping
273                         id = id_counter++;
274                         getBlockNodeIdMapping_mapping[global_id] = id;
275
276                         const ContentFeatures &f = nodedef->get(global_id);
277                         const std::string &name = f.name;
278                         if (name.empty())
279                                 unknown_contents.insert(global_id);
280                         else
281                                 nimap->set(id, name);
282                 }
283
284                 // Update the MapNode
285                 nodes[i].setContent(id);
286         }
287         for (u16 unknown_content : unknown_contents) {
288                 errorstream << "getBlockNodeIdMapping(): IGNORING ERROR: "
289                                 << "Name for node id " << unknown_content << " not known" << std::endl;
290         }
291 }
292 // Correct ids in the block to match nodedef based on names.
293 // Unknown ones are added to nodedef.
294 // Will not update itself to match id-name pairs in nodedef.
295 static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
296                 IGameDef *gamedef)
297 {
298         const NodeDefManager *nodedef = gamedef->ndef();
299         // This means the block contains incorrect ids, and we contain
300         // the information to convert those to names.
301         // nodedef contains information to convert our names to globally
302         // correct ids.
303         std::unordered_set<content_t> unnamed_contents;
304         std::unordered_set<std::string> unallocatable_contents;
305
306         bool previous_exists = false;
307         content_t previous_local_id = CONTENT_IGNORE;
308         content_t previous_global_id = CONTENT_IGNORE;
309
310         for (u32 i = 0; i < MapBlock::nodecount; i++) {
311                 content_t local_id = nodes[i].getContent();
312                 // If previous node local_id was found and same than before, don't lookup maps
313                 // apply directly previous resolved id
314                 // This permits to massively improve loading performance when nodes are similar
315                 // example: default:air, default:stone are massively present
316                 if (previous_exists && local_id == previous_local_id) {
317                         nodes[i].setContent(previous_global_id);
318                         continue;
319                 }
320
321                 std::string name;
322                 if (!nimap->getName(local_id, name)) {
323                         unnamed_contents.insert(local_id);
324                         previous_exists = false;
325                         continue;
326                 }
327
328                 content_t global_id;
329                 if (!nodedef->getId(name, global_id)) {
330                         global_id = gamedef->allocateUnknownNodeId(name);
331                         if (global_id == CONTENT_IGNORE) {
332                                 unallocatable_contents.insert(name);
333                                 previous_exists = false;
334                                 continue;
335                         }
336                 }
337                 nodes[i].setContent(global_id);
338
339                 // Save previous node local_id & global_id result
340                 previous_local_id = local_id;
341                 previous_global_id = global_id;
342                 previous_exists = true;
343         }
344
345         for (const content_t c: unnamed_contents) {
346                 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
347                                 << "Block contains id " << c
348                                 << " with no name mapping" << std::endl;
349         }
350         for (const std::string &node_name: unallocatable_contents) {
351                 errorstream << "correctBlockNodeIds(): IGNORING ERROR: "
352                                 << "Could not allocate global id for node name \""
353                                 << node_name << "\"" << std::endl;
354         }
355 }
356
357 void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
358 {
359         if(!ser_ver_supported(version))
360                 throw VersionMismatchException("ERROR: MapBlock format not supported");
361
362         if (!data)
363                 throw SerializationError("ERROR: Not writing dummy block.");
364
365         FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error");
366
367         // First byte
368         u8 flags = 0;
369         if(is_underground)
370                 flags |= 0x01;
371         if(getDayNightDiff())
372                 flags |= 0x02;
373         if (!m_generated)
374                 flags |= 0x08;
375         writeU8(os, flags);
376         if (version >= 27) {
377                 writeU16(os, m_lighting_complete);
378         }
379
380         /*
381                 Bulk node data
382         */
383         NameIdMapping nimap;
384         if(disk)
385         {
386                 MapNode *tmp_nodes = new MapNode[nodecount];
387                 for(u32 i=0; i<nodecount; i++)
388                         tmp_nodes[i] = data[i];
389                 getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
390
391                 u8 content_width = 2;
392                 u8 params_width = 2;
393                 writeU8(os, content_width);
394                 writeU8(os, params_width);
395                 MapNode::serializeBulk(os, version, tmp_nodes, nodecount,
396                                 content_width, params_width, true);
397                 delete[] tmp_nodes;
398         }
399         else
400         {
401                 u8 content_width = 2;
402                 u8 params_width = 2;
403                 writeU8(os, content_width);
404                 writeU8(os, params_width);
405                 MapNode::serializeBulk(os, version, data, nodecount,
406                                 content_width, params_width, true);
407         }
408
409         /*
410                 Node metadata
411         */
412         std::ostringstream oss(std::ios_base::binary);
413         m_node_metadata.serialize(oss, version, disk);
414         compressZlib(oss.str(), os);
415
416         /*
417                 Data that goes to disk, but not the network
418         */
419         if(disk)
420         {
421                 if(version <= 24){
422                         // Node timers
423                         m_node_timers.serialize(os, version);
424                 }
425
426                 // Static objects
427                 m_static_objects.serialize(os);
428
429                 // Timestamp
430                 writeU32(os, getTimestamp());
431
432                 // Write block-specific node definition id mapping
433                 nimap.serialize(os);
434
435                 if(version >= 25){
436                         // Node timers
437                         m_node_timers.serialize(os, version);
438                 }
439         }
440 }
441
442 void MapBlock::serializeNetworkSpecific(std::ostream &os)
443 {
444         if (!data) {
445                 throw SerializationError("ERROR: Not writing dummy block.");
446         }
447
448         writeU8(os, 1); // version
449         writeF1000(os, 0); // deprecated heat
450         writeF1000(os, 0); // deprecated humidity
451 }
452
453 void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
454 {
455         if(!ser_ver_supported(version))
456                 throw VersionMismatchException("ERROR: MapBlock format not supported");
457
458         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())<<std::endl);
459
460         m_day_night_differs_expired = false;
461
462         if(version <= 21)
463         {
464                 deSerialize_pre22(is, version, disk);
465                 return;
466         }
467
468         u8 flags = readU8(is);
469         is_underground = (flags & 0x01) != 0;
470         m_day_night_differs = (flags & 0x02) != 0;
471         if (version < 27)
472                 m_lighting_complete = 0xFFFF;
473         else
474                 m_lighting_complete = readU16(is);
475         m_generated = (flags & 0x08) == 0;
476
477         /*
478                 Bulk node data
479         */
480         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
481                         <<": Bulk node data"<<std::endl);
482         u8 content_width = readU8(is);
483         u8 params_width = readU8(is);
484         if(content_width != 1 && content_width != 2)
485                 throw SerializationError("MapBlock::deSerialize(): invalid content_width");
486         if(params_width != 2)
487                 throw SerializationError("MapBlock::deSerialize(): invalid params_width");
488         MapNode::deSerializeBulk(is, version, data, nodecount,
489                         content_width, params_width, true);
490
491         /*
492                 NodeMetadata
493         */
494         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
495                         <<": Node metadata"<<std::endl);
496         // Ignore errors
497         try {
498                 std::ostringstream oss(std::ios_base::binary);
499                 decompressZlib(is, oss);
500                 std::istringstream iss(oss.str(), std::ios_base::binary);
501                 if (version >= 23)
502                         m_node_metadata.deSerialize(iss, m_gamedef->idef());
503                 else
504                         content_nodemeta_deserialize_legacy(iss,
505                                 &m_node_metadata, &m_node_timers,
506                                 m_gamedef->idef());
507         } catch(SerializationError &e) {
508                 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
509                                 <<" while deserializing node metadata at ("
510                                 <<PP(getPos())<<": "<<e.what()<<std::endl;
511         }
512
513         /*
514                 Data that is only on disk
515         */
516         if(disk)
517         {
518                 // Node timers
519                 if(version == 23){
520                         // Read unused zero
521                         readU8(is);
522                 }
523                 if(version == 24){
524                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
525                                         <<": Node timers (ver==24)"<<std::endl);
526                         m_node_timers.deSerialize(is, version);
527                 }
528
529                 // Static objects
530                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
531                                 <<": Static objects"<<std::endl);
532                 m_static_objects.deSerialize(is);
533
534                 // Timestamp
535                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
536                                 <<": Timestamp"<<std::endl);
537                 setTimestamp(readU32(is));
538                 m_disk_timestamp = m_timestamp;
539
540                 // Dynamically re-set ids based on node names
541                 TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
542                                 <<": NameIdMapping"<<std::endl);
543                 NameIdMapping nimap;
544                 nimap.deSerialize(is);
545                 correctBlockNodeIds(&nimap, data, m_gamedef);
546
547                 if(version >= 25){
548                         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
549                                         <<": Node timers (ver>=25)"<<std::endl);
550                         m_node_timers.deSerialize(is, version);
551                 }
552         }
553
554         TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
555                         <<": Done."<<std::endl);
556 }
557
558 void MapBlock::deSerializeNetworkSpecific(std::istream &is)
559 {
560         try {
561                 int version = readU8(is);
562                 //if(version != 1)
563                 //      throw SerializationError("unsupported MapBlock version");
564                 if(version >= 1) {
565                         readF1000(is); // deprecated heat
566                         readF1000(is); // deprecated humidity
567                 }
568         }
569         catch(SerializationError &e)
570         {
571                 warningstream<<"MapBlock::deSerializeNetworkSpecific(): Ignoring an error"
572                                 <<": "<<e.what()<<std::endl;
573         }
574 }
575
576 /*
577         Legacy serialization
578 */
579
580 void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
581 {
582         // Initialize default flags
583         is_underground = false;
584         m_day_night_differs = false;
585         m_lighting_complete = 0xFFFF;
586         m_generated = true;
587
588         // Make a temporary buffer
589         u32 ser_length = MapNode::serializedLength(version);
590         SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
591
592         // These have no compression
593         if (version <= 3 || version == 5 || version == 6) {
594                 char tmp;
595                 is.read(&tmp, 1);
596                 if (is.gcount() != 1)
597                         throw SerializationError(std::string(FUNCTION_NAME)
598                                 + ": not enough input data");
599                 is_underground = tmp;
600                 is.read((char *)*databuf_nodelist, nodecount * ser_length);
601                 if ((u32)is.gcount() != nodecount * ser_length)
602                         throw SerializationError(std::string(FUNCTION_NAME)
603                                 + ": not enough input data");
604         } else if (version <= 10) {
605                 u8 t8;
606                 is.read((char *)&t8, 1);
607                 is_underground = t8;
608
609                 {
610                         // Uncompress and set material data
611                         std::ostringstream os(std::ios_base::binary);
612                         decompress(is, os, version);
613                         std::string s = os.str();
614                         if (s.size() != nodecount)
615                                 throw SerializationError(std::string(FUNCTION_NAME)
616                                         + ": not enough input data");
617                         for (u32 i = 0; i < s.size(); i++) {
618                                 databuf_nodelist[i*ser_length] = s[i];
619                         }
620                 }
621                 {
622                         // Uncompress and set param data
623                         std::ostringstream os(std::ios_base::binary);
624                         decompress(is, os, version);
625                         std::string s = os.str();
626                         if (s.size() != nodecount)
627                                 throw SerializationError(std::string(FUNCTION_NAME)
628                                         + ": not enough input data");
629                         for (u32 i = 0; i < s.size(); i++) {
630                                 databuf_nodelist[i*ser_length + 1] = s[i];
631                         }
632                 }
633
634                 if (version >= 10) {
635                         // Uncompress and set param2 data
636                         std::ostringstream os(std::ios_base::binary);
637                         decompress(is, os, version);
638                         std::string s = os.str();
639                         if (s.size() != nodecount)
640                                 throw SerializationError(std::string(FUNCTION_NAME)
641                                         + ": not enough input data");
642                         for (u32 i = 0; i < s.size(); i++) {
643                                 databuf_nodelist[i*ser_length + 2] = s[i];
644                         }
645                 }
646         } else { // All other versions (10 to 21)
647                 u8 flags;
648                 is.read((char*)&flags, 1);
649                 is_underground = (flags & 0x01) != 0;
650                 m_day_night_differs = (flags & 0x02) != 0;
651                 if(version >= 18)
652                         m_generated = (flags & 0x08) == 0;
653
654                 // Uncompress data
655                 std::ostringstream os(std::ios_base::binary);
656                 decompress(is, os, version);
657                 std::string s = os.str();
658                 if (s.size() != nodecount * 3)
659                         throw SerializationError(std::string(FUNCTION_NAME)
660                                 + ": decompress resulted in size other than nodecount*3");
661
662                 // deserialize nodes from buffer
663                 for (u32 i = 0; i < nodecount; i++) {
664                         databuf_nodelist[i*ser_length] = s[i];
665                         databuf_nodelist[i*ser_length + 1] = s[i+nodecount];
666                         databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2];
667                 }
668
669                 /*
670                         NodeMetadata
671                 */
672                 if (version >= 14) {
673                         // Ignore errors
674                         try {
675                                 if (version <= 15) {
676                                         std::string data = deSerializeString(is);
677                                         std::istringstream iss(data, std::ios_base::binary);
678                                         content_nodemeta_deserialize_legacy(iss,
679                                                 &m_node_metadata, &m_node_timers,
680                                                 m_gamedef->idef());
681                                 } else {
682                                         //std::string data = deSerializeLongString(is);
683                                         std::ostringstream oss(std::ios_base::binary);
684                                         decompressZlib(is, oss);
685                                         std::istringstream iss(oss.str(), std::ios_base::binary);
686                                         content_nodemeta_deserialize_legacy(iss,
687                                                 &m_node_metadata, &m_node_timers,
688                                                 m_gamedef->idef());
689                                 }
690                         } catch(SerializationError &e) {
691                                 warningstream<<"MapBlock::deSerialize(): Ignoring an error"
692                                                 <<" while deserializing node metadata"<<std::endl;
693                         }
694                 }
695         }
696
697         // Deserialize node data
698         for (u32 i = 0; i < nodecount; i++) {
699                 data[i].deSerialize(&databuf_nodelist[i * ser_length], version);
700         }
701
702         if (disk) {
703                 /*
704                         Versions up from 9 have block objects. (DEPRECATED)
705                 */
706                 if (version >= 9) {
707                         u16 count = readU16(is);
708                         // Not supported and length not known if count is not 0
709                         if(count != 0){
710                                 warningstream<<"MapBlock::deSerialize_pre22(): "
711                                                 <<"Ignoring stuff coming at and after MBOs"<<std::endl;
712                                 return;
713                         }
714                 }
715
716                 /*
717                         Versions up from 15 have static objects.
718                 */
719                 if (version >= 15)
720                         m_static_objects.deSerialize(is);
721
722                 // Timestamp
723                 if (version >= 17) {
724                         setTimestamp(readU32(is));
725                         m_disk_timestamp = m_timestamp;
726                 } else {
727                         setTimestamp(BLOCK_TIMESTAMP_UNDEFINED);
728                 }
729
730                 // Dynamically re-set ids based on node names
731                 NameIdMapping nimap;
732                 // If supported, read node definition id mapping
733                 if (version >= 21) {
734                         nimap.deSerialize(is);
735                 // Else set the legacy mapping
736                 } else {
737                         content_mapnode_get_name_id_mapping(&nimap);
738                 }
739                 correctBlockNodeIds(&nimap, data, m_gamedef);
740         }
741
742
743         // Legacy data changes
744         // This code has to convert from pre-22 to post-22 format.
745         const NodeDefManager *nodedef = m_gamedef->ndef();
746         for(u32 i=0; i<nodecount; i++)
747         {
748                 const ContentFeatures &f = nodedef->get(data[i].getContent());
749                 // Mineral
750                 if(nodedef->getId("default:stone") == data[i].getContent()
751                                 && data[i].getParam1() == 1)
752                 {
753                         data[i].setContent(nodedef->getId("default:stone_with_coal"));
754                         data[i].setParam1(0);
755                 }
756                 else if(nodedef->getId("default:stone") == data[i].getContent()
757                                 && data[i].getParam1() == 2)
758                 {
759                         data[i].setContent(nodedef->getId("default:stone_with_iron"));
760                         data[i].setParam1(0);
761                 }
762                 // facedir_simple
763                 if(f.legacy_facedir_simple)
764                 {
765                         data[i].setParam2(data[i].getParam1());
766                         data[i].setParam1(0);
767                 }
768                 // wall_mounted
769                 if(f.legacy_wallmounted)
770                 {
771                         u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
772                         u8 dir_old_format = data[i].getParam2();
773                         u8 dir_new_format = 0;
774                         for(u8 j=0; j<8; j++)
775                         {
776                                 if((dir_old_format & wallmounted_new_to_old[j]) != 0)
777                                 {
778                                         dir_new_format = j;
779                                         break;
780                                 }
781                         }
782                         data[i].setParam2(dir_new_format);
783                 }
784         }
785
786 }
787
788 /*
789         Get a quick string to describe what a block actually contains
790 */
791 std::string analyze_block(MapBlock *block)
792 {
793         if(block == NULL)
794                 return "NULL";
795
796         std::ostringstream desc;
797
798         v3s16 p = block->getPos();
799         char spos[25];
800         snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z);
801         desc<<spos;
802
803         switch(block->getModified())
804         {
805         case MOD_STATE_CLEAN:
806                 desc<<"CLEAN,           ";
807                 break;
808         case MOD_STATE_WRITE_AT_UNLOAD:
809                 desc<<"WRITE_AT_UNLOAD, ";
810                 break;
811         case MOD_STATE_WRITE_NEEDED:
812                 desc<<"WRITE_NEEDED,    ";
813                 break;
814         default:
815                 desc<<"unknown getModified()="+itos(block->getModified())+", ";
816         }
817
818         if(block->isGenerated())
819                 desc<<"is_gen [X], ";
820         else
821                 desc<<"is_gen [ ], ";
822
823         if(block->getIsUnderground())
824                 desc<<"is_ug [X], ";
825         else
826                 desc<<"is_ug [ ], ";
827
828         desc<<"lighting_complete: "<<block->getLightingComplete()<<", ";
829
830         if(block->isDummy())
831         {
832                 desc<<"Dummy, ";
833         }
834         else
835         {
836                 bool full_ignore = true;
837                 bool some_ignore = false;
838                 bool full_air = true;
839                 bool some_air = false;
840                 for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
841                 for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
842                 for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
843                 {
844                         v3s16 p(x0,y0,z0);
845                         MapNode n = block->getNodeNoEx(p);
846                         content_t c = n.getContent();
847                         if(c == CONTENT_IGNORE)
848                                 some_ignore = true;
849                         else
850                                 full_ignore = false;
851                         if(c == CONTENT_AIR)
852                                 some_air = true;
853                         else
854                                 full_air = false;
855                 }
856
857                 desc<<"content {";
858
859                 std::ostringstream ss;
860
861                 if(full_ignore)
862                         ss<<"IGNORE (full), ";
863                 else if(some_ignore)
864                         ss<<"IGNORE, ";
865
866                 if(full_air)
867                         ss<<"AIR (full), ";
868                 else if(some_air)
869                         ss<<"AIR, ";
870
871                 if(ss.str().size()>=2)
872                         desc<<ss.str().substr(0, ss.str().size()-2);
873
874                 desc<<"}, ";
875         }
876
877         return desc.str().substr(0, desc.str().size()-2);
878 }
879
880
881 //END